@hema-to/regl-scatterplot 1.14.1 → 1.16.0-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 +94 -0
- package/dist/regl-scatterplot.esm.d.ts +2 -0
- package/dist/regl-scatterplot.esm.js +1678 -1538
- package/dist/regl-scatterplot.js +1747 -1756
- package/dist/regl-scatterplot.min.js +3 -8
- package/dist/types.d.ts +6 -7
- package/package.json +27 -27
- package/src/constants.js +1 -0
- package/src/index.js +217 -43
- package/src/kdbush-worker.js +0 -1
- package/src/kdbush.js +2 -3
- package/src/lasso-manager/index.js +6 -8
- package/src/renderer.js +2 -22
- package/src/spline-curve-worker.js +0 -3
- package/src/types.d.ts +6 -7
- package/src/utils.js +40 -4
|
@@ -1,90 +1,93 @@
|
|
|
1
1
|
import createPubSub from 'pub-sub-es';
|
|
2
2
|
import createOriginalRegl from 'regl';
|
|
3
3
|
|
|
4
|
-
// @flekschas/utils v0.32.2 Copyright 2023 Fritz Lekschas
|
|
5
4
|
/* eslint no-param-reassign:0 */
|
|
6
|
-
|
|
7
5
|
/**
|
|
8
6
|
* Cubic in easing function
|
|
9
|
-
* @param
|
|
7
|
+
* @param t - The input time to be eased. Must be in [0, 1] where `0`
|
|
10
8
|
* refers to the start and `1` to the end
|
|
11
|
-
* @return
|
|
9
|
+
* @return The eased time
|
|
12
10
|
*/
|
|
13
11
|
const cubicIn = (t) => t * t * t;
|
|
14
|
-
|
|
15
12
|
/**
|
|
16
13
|
* Cubic in and out easing function
|
|
17
|
-
* @param
|
|
14
|
+
* @param t - The input time to be eased. Must be in [0, 1] where `0`
|
|
18
15
|
* refers to the start and `1` to the end
|
|
19
|
-
* @return
|
|
16
|
+
* @return The eased time
|
|
20
17
|
*/
|
|
21
|
-
const cubicInOut = (t) =>
|
|
22
|
-
t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
|
|
23
|
-
|
|
18
|
+
const cubicInOut = (t) => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
|
|
24
19
|
/**
|
|
25
20
|
* Cubic out easing function
|
|
26
|
-
* @param
|
|
21
|
+
* @param t - The input time to be eased. Must be in [0, 1] where `0`
|
|
27
22
|
* refers to the start and `1` to the end
|
|
28
|
-
* @return
|
|
23
|
+
* @return The eased time
|
|
29
24
|
*/
|
|
30
25
|
const cubicOut = (t) => --t * t * t + 1;
|
|
31
|
-
|
|
32
26
|
/**
|
|
33
27
|
* Linear easing function
|
|
34
|
-
* @param
|
|
28
|
+
* @param t - The input time to be eased. Must be in [0, 1] where `0`
|
|
35
29
|
* refers to the start and `1` to the end
|
|
36
|
-
* @return
|
|
30
|
+
* @return Same as the input
|
|
37
31
|
*/
|
|
38
32
|
const linear = (t) => t;
|
|
39
|
-
|
|
40
33
|
/**
|
|
41
34
|
* Quadratic in easing function
|
|
42
|
-
* @param
|
|
35
|
+
* @param t - The input time to be eased. Must be in [0, 1] where `0`
|
|
43
36
|
* refers to the start and `1` to the end
|
|
44
|
-
* @return
|
|
37
|
+
* @return The eased time
|
|
45
38
|
*/
|
|
46
39
|
const quadIn = (t) => t * t;
|
|
47
|
-
|
|
48
40
|
/**
|
|
49
41
|
* Quadratic in and out easing function
|
|
50
|
-
* @param
|
|
42
|
+
* @param t - The input time to be eased. Must be in [0, 1] where `0`
|
|
51
43
|
* refers to the start and `1` to the end
|
|
52
|
-
* @return
|
|
44
|
+
* @return The eased time
|
|
53
45
|
*/
|
|
54
|
-
const quadInOut = (t) =>
|
|
55
|
-
|
|
46
|
+
const quadInOut = (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
56
47
|
/**
|
|
57
48
|
* Quadratic out easing function
|
|
58
|
-
* @param
|
|
49
|
+
* @param t - The input time to be eased. Must be in [0, 1] where `0`
|
|
59
50
|
* refers to the start and `1` to the end
|
|
60
|
-
* @return
|
|
51
|
+
* @return The eased time
|
|
61
52
|
*/
|
|
62
53
|
const quadOut = (t) => t * (2 - t);
|
|
63
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Restrict value to be within [min, max]
|
|
57
|
+
* @description About 18% faster than `Math.max(min, Math.min(max, value))`
|
|
58
|
+
* @param value - Value to be clamped
|
|
59
|
+
* @param min - Min value
|
|
60
|
+
* @param max - Max value
|
|
61
|
+
* @return Clamped value
|
|
62
|
+
*/
|
|
64
63
|
/**
|
|
65
64
|
* Identity function
|
|
66
|
-
* @
|
|
67
|
-
* @
|
|
68
|
-
* @return {*} `x`
|
|
65
|
+
* @param x - Any kind of value
|
|
66
|
+
* @return `x`
|
|
69
67
|
*/
|
|
70
68
|
const identity = (x) => x;
|
|
71
69
|
|
|
72
70
|
/**
|
|
73
71
|
* Check if two arrays contain the same elements
|
|
74
|
-
* @
|
|
75
|
-
* @param
|
|
76
|
-
* @
|
|
77
|
-
* @return {boolean} If `true` the two arrays contain the same elements
|
|
72
|
+
* @param a - First array
|
|
73
|
+
* @param b - Second array
|
|
74
|
+
* @return If `true` the two arrays contain the same elements
|
|
78
75
|
*/
|
|
79
76
|
const hasSameElements = (a, b) => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
if (a === b) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
if (a.length !== b.length) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const aSet = new Set(a);
|
|
84
|
+
const bSet = new Set(b);
|
|
85
|
+
// Since the arrays could contain duplicates, we have to check the set length
|
|
86
|
+
// as well
|
|
87
|
+
if (aSet.size !== bSet.size) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return b.every((element) => aSet.has(element));
|
|
88
91
|
};
|
|
89
92
|
|
|
90
93
|
/**
|
|
@@ -93,11 +96,10 @@ const hasSameElements = (a, b) => {
|
|
|
93
96
|
* @description
|
|
94
97
|
* This is identical but much faster than `Math.hypot(...v)`
|
|
95
98
|
*
|
|
96
|
-
* @param
|
|
97
|
-
* @return
|
|
99
|
+
* @param v - Numerical vector
|
|
100
|
+
* @return L2 norm
|
|
98
101
|
*/
|
|
99
102
|
const l2Norm = (v) => Math.sqrt(v.reduce((sum, x) => sum + x ** 2, 0));
|
|
100
|
-
|
|
101
103
|
/**
|
|
102
104
|
* Get the maximum number of a vector while ignoring NaNs
|
|
103
105
|
*
|
|
@@ -105,164 +107,158 @@ const l2Norm = (v) => Math.sqrt(v.reduce((sum, x) => sum + x ** 2, 0));
|
|
|
105
107
|
* This version is muuuch faster than `Math.max(...v)` and supports vectors
|
|
106
108
|
* longer than 256^2, which is a limitation of `Math.max.apply(null, v)`.
|
|
107
109
|
*
|
|
108
|
-
* @param
|
|
109
|
-
* @return
|
|
110
|
+
* @param v - Numerical vector
|
|
111
|
+
* @return The largest number
|
|
110
112
|
*/
|
|
111
|
-
const max$1 = (v) =>
|
|
112
|
-
v.reduce((_max, a) => (a > _max ? a : _max), -Infinity);
|
|
113
|
-
|
|
113
|
+
const max$1 = (v) => v.reduce((_max, a) => (a > _max ? a : _max), Number.NEGATIVE_INFINITY);
|
|
114
114
|
/**
|
|
115
115
|
* Initialize an array of a certain length using a mapping function
|
|
116
116
|
*
|
|
117
117
|
* @description
|
|
118
118
|
* This is equivalent to `Array.from({ length }, mapFn)` but about 60% faster
|
|
119
119
|
*
|
|
120
|
-
* @param
|
|
121
|
-
* @param
|
|
122
|
-
* @return
|
|
123
|
-
* @type {<T = number>(length: number, mapFn: (i: number, length: number) => T) => T[]}
|
|
120
|
+
* @param length - Size of the array
|
|
121
|
+
* @param mapFn - Mapping function
|
|
122
|
+
* @return Initialized array
|
|
124
123
|
*/
|
|
125
|
-
const rangeMap = (length, mapFn =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
const rangeMap = (length, mapFn = identity) => {
|
|
125
|
+
const out = [];
|
|
126
|
+
for (let i = 0; i < length; i++) {
|
|
127
|
+
out.push(mapFn(i, length));
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
131
130
|
};
|
|
132
|
-
|
|
133
131
|
/**
|
|
134
132
|
* Get the unique union of two vectors of integers
|
|
135
|
-
* @param
|
|
136
|
-
* @param
|
|
137
|
-
* @return
|
|
133
|
+
* @param v - First vector of integers
|
|
134
|
+
* @param w - Second vector of integers
|
|
135
|
+
* @return Unique union of `v` and `w`
|
|
138
136
|
*/
|
|
139
137
|
const unionIntegers = (v, w) => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
138
|
+
const a = [];
|
|
139
|
+
v.forEach((x) => {
|
|
140
|
+
a[x] = true;
|
|
141
|
+
});
|
|
142
|
+
w.forEach((x) => {
|
|
143
|
+
a[x] = true;
|
|
144
|
+
});
|
|
145
|
+
return a.reduce((union, value, i) => {
|
|
146
|
+
if (value)
|
|
147
|
+
union.push(i);
|
|
148
|
+
return union;
|
|
149
|
+
}, []);
|
|
151
150
|
};
|
|
152
151
|
|
|
153
152
|
/**
|
|
154
153
|
* Assign properties, constructors, etc. to an object
|
|
155
154
|
*
|
|
156
|
-
* @param
|
|
157
|
-
* @param
|
|
158
|
-
* @return
|
|
155
|
+
* @param target - The target object that gets `sources` assigned to it
|
|
156
|
+
* @param sources - The sources to be assigned to the target object
|
|
157
|
+
* @return An object representing the union of the target and sources objects
|
|
159
158
|
*/
|
|
160
159
|
const assign = (target, ...sources) => {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
160
|
+
sources.forEach((source) => {
|
|
161
|
+
// eslint-disable-next-line no-shadow
|
|
162
|
+
const descriptors = Object.keys(source).reduce((descriptors, key) => {
|
|
163
|
+
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
|
|
164
|
+
return descriptors;
|
|
165
|
+
}, {});
|
|
166
|
+
// By default, Object.assign copies enumerable Symbols, too
|
|
167
|
+
Object.getOwnPropertySymbols(source).forEach((symbol) => {
|
|
168
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, symbol);
|
|
169
|
+
if (descriptor?.enumerable) {
|
|
170
|
+
descriptors[symbol] = descriptor;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
Object.defineProperties(target, descriptors);
|
|
174
174
|
});
|
|
175
|
-
|
|
176
|
-
});
|
|
177
|
-
return target;
|
|
175
|
+
return target;
|
|
178
176
|
};
|
|
179
177
|
|
|
180
178
|
/**
|
|
181
179
|
* Convenience function to compose functions
|
|
182
|
-
* @param
|
|
183
|
-
* @return
|
|
180
|
+
* @param fns - Array of functions
|
|
181
|
+
* @return The composed function
|
|
184
182
|
*/
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
* @param {*} x - Some value
|
|
189
|
-
* @return {*} Output of the composed function
|
|
190
|
-
*/
|
|
191
|
-
(x) =>
|
|
192
|
-
fns.reduce((y, f) => f(y), x);
|
|
193
|
-
|
|
183
|
+
function pipe(...fns) {
|
|
184
|
+
return (x) => fns.reduce((y, f) => f(y), x);
|
|
185
|
+
}
|
|
194
186
|
/**
|
|
195
187
|
* Assign a constructor to the object
|
|
196
|
-
* @param
|
|
188
|
+
* @param constructorFn - Constructor functions
|
|
197
189
|
*/
|
|
198
|
-
const withConstructor = (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
__proto__: {
|
|
202
|
-
constructor,
|
|
203
|
-
},
|
|
190
|
+
const withConstructor = (constructorFn) => (self) => assign({
|
|
191
|
+
__proto__: {
|
|
192
|
+
constructor: constructorFn,
|
|
204
193
|
},
|
|
205
|
-
|
|
206
|
-
);
|
|
207
|
-
|
|
194
|
+
}, self);
|
|
208
195
|
/**
|
|
209
196
|
* Assign a static property to an object
|
|
210
|
-
* @param
|
|
211
|
-
* @param
|
|
197
|
+
* @param name - Name of the property
|
|
198
|
+
* @param value - Static value
|
|
212
199
|
*/
|
|
213
|
-
const withStaticProperty = (name, value) => (self) =>
|
|
214
|
-
assign(self, {
|
|
200
|
+
const withStaticProperty = (name, value) => (self) => assign(self, {
|
|
215
201
|
get [name]() {
|
|
216
|
-
|
|
202
|
+
return value;
|
|
217
203
|
},
|
|
218
|
-
|
|
204
|
+
});
|
|
219
205
|
|
|
206
|
+
/**
|
|
207
|
+
* L distance between a pair of vectors
|
|
208
|
+
*
|
|
209
|
+
* @description
|
|
210
|
+
* Identical but much faster than `lDist(l)([fromX, fromY], [toX, toY])`
|
|
211
|
+
*
|
|
212
|
+
* @param l - Defines the Lp space
|
|
213
|
+
*/
|
|
220
214
|
/**
|
|
221
215
|
* L2 distance between a pair of points
|
|
222
216
|
*
|
|
223
217
|
* @description
|
|
224
218
|
* Identical but much faster than `l2Dist([fromX, fromY], [toX, toY])`
|
|
225
219
|
*
|
|
226
|
-
* @
|
|
227
|
-
* @
|
|
228
|
-
* @
|
|
229
|
-
* @
|
|
230
|
-
* @
|
|
220
|
+
* @paramfromX - X coordinate of the first point
|
|
221
|
+
* @paramfromY - Y coordinate of the first point
|
|
222
|
+
* @paramtoX - X coordinate of the second point
|
|
223
|
+
* @paramtoY - Y coordinate of the first point
|
|
224
|
+
* @returnL2 distance
|
|
231
225
|
*/
|
|
232
|
-
const l2PointDist = (fromX, fromY, toX, toY) =>
|
|
233
|
-
Math.sqrt((fromX - toX) ** 2 + (fromY - toY) ** 2);
|
|
226
|
+
const l2PointDist = (fromX, fromY, toX, toY) => Math.sqrt((fromX - toX) ** 2 + (fromY - toY) ** 2);
|
|
234
227
|
|
|
235
228
|
/**
|
|
236
229
|
* Create a worker from a function
|
|
237
|
-
* @param
|
|
238
|
-
* @return
|
|
230
|
+
* @param fn - Function to be turned into a worker
|
|
231
|
+
* @return Worker function
|
|
239
232
|
*/
|
|
240
|
-
const createWorker$1 = (fn) =>
|
|
241
|
-
new Worker(
|
|
242
|
-
window.URL.createObjectURL(
|
|
243
|
-
new Blob([`(${fn.toString()})()`], { type: 'text/javascript' })
|
|
244
|
-
)
|
|
245
|
-
);
|
|
233
|
+
const createWorker$1 = (fn) => new Worker(window.URL.createObjectURL(new Blob([`(${fn.toString()})()`], { type: "text/javascript" })));
|
|
246
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Debounce a function call.
|
|
237
|
+
*
|
|
238
|
+
* @description
|
|
239
|
+
* Function calls are delayed by `wait` milliseconds and only one out of
|
|
240
|
+
* multiple function calls is executed.
|
|
241
|
+
*
|
|
242
|
+
* @param fn - Function to be debounced
|
|
243
|
+
* @param wait - Number of milliseconds to debounce the function call.
|
|
244
|
+
* @return Debounced function
|
|
245
|
+
*/
|
|
247
246
|
/**
|
|
248
247
|
* Get a promise that resolves after the next `n` animation frames
|
|
249
|
-
* @param
|
|
250
|
-
* @return
|
|
248
|
+
* @param n - Number of animation frames to wait
|
|
249
|
+
* @return A promise that resolves after the next `n` animation frames
|
|
251
250
|
*/
|
|
252
|
-
const nextAnimationFrame = (n = 1) =>
|
|
253
|
-
new Promise((resolve) => {
|
|
251
|
+
const nextAnimationFrame = (n = 1) => new Promise((resolve) => {
|
|
254
252
|
let i = 0;
|
|
255
|
-
|
|
256
|
-
const raf = () =>
|
|
257
|
-
requestAnimationFrame(() => {
|
|
253
|
+
const raf = () => requestAnimationFrame(() => {
|
|
258
254
|
i++;
|
|
259
|
-
if (i < n)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
255
|
+
if (i < n)
|
|
256
|
+
raf();
|
|
257
|
+
else
|
|
258
|
+
resolve(undefined);
|
|
259
|
+
});
|
|
263
260
|
raf();
|
|
264
|
-
|
|
265
|
-
|
|
261
|
+
});
|
|
266
262
|
/**
|
|
267
263
|
* Throttle and debounce a function call
|
|
268
264
|
*
|
|
@@ -285,83 +281,64 @@ const nextAnimationFrame = (n = 1) =>
|
|
|
285
281
|
* 6. No call => nothing
|
|
286
282
|
* 7. No call => f(args5) called due to debouncing
|
|
287
283
|
*
|
|
288
|
-
* @param
|
|
289
|
-
* @param
|
|
290
|
-
* @param
|
|
291
|
-
*
|
|
292
|
-
* @return
|
|
284
|
+
* @param fn - Function to be throttled and debounced
|
|
285
|
+
* @param throttleTime - Throttle intevals in milliseconds
|
|
286
|
+
* @param debounceTime - Debounce wait time in milliseconds. By default this is
|
|
287
|
+
* the same as `throttleTime`.
|
|
288
|
+
* @return Throttled and debounced function
|
|
293
289
|
*/
|
|
294
|
-
const throttleAndDebounce = (fn, throttleTime, debounceTime
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
290
|
+
const throttleAndDebounce = (fn, throttleTime, debounceTime) => {
|
|
291
|
+
let timeout;
|
|
292
|
+
let blockedCalls = 0;
|
|
293
|
+
debounceTime = debounceTime === null ? throttleTime : debounceTime;
|
|
294
|
+
const debounced = (...args) => {
|
|
295
|
+
const later = () => {
|
|
296
|
+
// Since we throttle and debounce we should check whether there were
|
|
297
|
+
// actually multiple attempts to call this function after the most recent
|
|
298
|
+
// throttled call. If there were no more calls we don't have to call
|
|
299
|
+
// the function again.
|
|
300
|
+
if (blockedCalls > 0) {
|
|
301
|
+
fn(...args);
|
|
302
|
+
blockedCalls = 0;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
clearTimeout(timeout);
|
|
306
|
+
timeout = setTimeout(later, debounceTime);
|
|
311
307
|
};
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
308
|
+
let isWaiting = false;
|
|
309
|
+
const throttledAndDebounced = (...args) => {
|
|
310
|
+
if (isWaiting) {
|
|
311
|
+
blockedCalls++;
|
|
312
|
+
debounced(...args);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
fn(...args);
|
|
316
|
+
debounced(...args);
|
|
317
|
+
isWaiting = true;
|
|
318
|
+
blockedCalls = 0;
|
|
319
|
+
setTimeout(() => {
|
|
320
|
+
isWaiting = false;
|
|
321
|
+
}, throttleTime);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
throttledAndDebounced.reset = () => {
|
|
327
325
|
isWaiting = false;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
throttledAndDebounced.reset = () => {
|
|
336
|
-
isWaiting = false;
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
throttledAndDebounced.cancel = () => {
|
|
340
|
-
clearTimeout(timeout);
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
throttledAndDebounced.now = (...args) => fn(...args);
|
|
344
|
-
|
|
345
|
-
return throttledAndDebounced;
|
|
326
|
+
};
|
|
327
|
+
throttledAndDebounced.cancel = () => {
|
|
328
|
+
clearTimeout(timeout);
|
|
329
|
+
};
|
|
330
|
+
throttledAndDebounced.now = (...args) => fn(...args);
|
|
331
|
+
return throttledAndDebounced;
|
|
346
332
|
};
|
|
347
333
|
|
|
348
334
|
/**
|
|
349
335
|
* Common utilities
|
|
350
336
|
* @module glMatrix
|
|
351
337
|
*/
|
|
338
|
+
|
|
352
339
|
// Configuration Constants
|
|
353
340
|
var EPSILON = 0.000001;
|
|
354
|
-
var ARRAY_TYPE = typeof Float32Array !==
|
|
355
|
-
if (!Math.hypot) Math.hypot = function () {
|
|
356
|
-
var y = 0,
|
|
357
|
-
i = arguments.length;
|
|
358
|
-
|
|
359
|
-
while (i--) {
|
|
360
|
-
y += arguments[i] * arguments[i];
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return Math.sqrt(y);
|
|
364
|
-
};
|
|
341
|
+
var ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array;
|
|
365
342
|
|
|
366
343
|
/**
|
|
367
344
|
* 4x4 Matrix<br>Format: column-major, when typed out it looks like row-major<br>The matrices are being post multiplied.
|
|
@@ -373,10 +350,8 @@ if (!Math.hypot) Math.hypot = function () {
|
|
|
373
350
|
*
|
|
374
351
|
* @returns {mat4} a new 4x4 matrix
|
|
375
352
|
*/
|
|
376
|
-
|
|
377
353
|
function create$2() {
|
|
378
354
|
var out = new ARRAY_TYPE(16);
|
|
379
|
-
|
|
380
355
|
if (ARRAY_TYPE != Float32Array) {
|
|
381
356
|
out[1] = 0;
|
|
382
357
|
out[2] = 0;
|
|
@@ -391,20 +366,19 @@ function create$2() {
|
|
|
391
366
|
out[13] = 0;
|
|
392
367
|
out[14] = 0;
|
|
393
368
|
}
|
|
394
|
-
|
|
395
369
|
out[0] = 1;
|
|
396
370
|
out[5] = 1;
|
|
397
371
|
out[10] = 1;
|
|
398
372
|
out[15] = 1;
|
|
399
373
|
return out;
|
|
400
374
|
}
|
|
375
|
+
|
|
401
376
|
/**
|
|
402
377
|
* Creates a new mat4 initialized with values from an existing matrix
|
|
403
378
|
*
|
|
404
379
|
* @param {ReadonlyMat4} a matrix to clone
|
|
405
380
|
* @returns {mat4} a new 4x4 matrix
|
|
406
381
|
*/
|
|
407
|
-
|
|
408
382
|
function clone(a) {
|
|
409
383
|
var out = new ARRAY_TYPE(16);
|
|
410
384
|
out[0] = a[0];
|
|
@@ -425,31 +399,31 @@ function clone(a) {
|
|
|
425
399
|
out[15] = a[15];
|
|
426
400
|
return out;
|
|
427
401
|
}
|
|
402
|
+
|
|
428
403
|
/**
|
|
429
404
|
* Inverts a mat4
|
|
430
405
|
*
|
|
431
406
|
* @param {mat4} out the receiving matrix
|
|
432
407
|
* @param {ReadonlyMat4} a the source matrix
|
|
433
|
-
* @returns {mat4} out
|
|
408
|
+
* @returns {mat4 | null} out, or null if source matrix is not invertible
|
|
434
409
|
*/
|
|
435
|
-
|
|
436
410
|
function invert(out, a) {
|
|
437
411
|
var a00 = a[0],
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
412
|
+
a01 = a[1],
|
|
413
|
+
a02 = a[2],
|
|
414
|
+
a03 = a[3];
|
|
441
415
|
var a10 = a[4],
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
416
|
+
a11 = a[5],
|
|
417
|
+
a12 = a[6],
|
|
418
|
+
a13 = a[7];
|
|
445
419
|
var a20 = a[8],
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
420
|
+
a21 = a[9],
|
|
421
|
+
a22 = a[10],
|
|
422
|
+
a23 = a[11];
|
|
449
423
|
var a30 = a[12],
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
424
|
+
a31 = a[13],
|
|
425
|
+
a32 = a[14],
|
|
426
|
+
a33 = a[15];
|
|
453
427
|
var b00 = a00 * a11 - a01 * a10;
|
|
454
428
|
var b01 = a00 * a12 - a02 * a10;
|
|
455
429
|
var b02 = a00 * a13 - a03 * a10;
|
|
@@ -461,14 +435,13 @@ function invert(out, a) {
|
|
|
461
435
|
var b08 = a20 * a33 - a23 * a30;
|
|
462
436
|
var b09 = a21 * a32 - a22 * a31;
|
|
463
437
|
var b10 = a21 * a33 - a23 * a31;
|
|
464
|
-
var b11 = a22 * a33 - a23 * a32;
|
|
438
|
+
var b11 = a22 * a33 - a23 * a32;
|
|
465
439
|
|
|
440
|
+
// Calculate the determinant
|
|
466
441
|
var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
|
|
467
|
-
|
|
468
442
|
if (!det) {
|
|
469
443
|
return null;
|
|
470
444
|
}
|
|
471
|
-
|
|
472
445
|
det = 1.0 / det;
|
|
473
446
|
out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
|
|
474
447
|
out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
|
|
@@ -488,6 +461,7 @@ function invert(out, a) {
|
|
|
488
461
|
out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
|
|
489
462
|
return out;
|
|
490
463
|
}
|
|
464
|
+
|
|
491
465
|
/**
|
|
492
466
|
* Multiplies two mat4s
|
|
493
467
|
*
|
|
@@ -496,29 +470,29 @@ function invert(out, a) {
|
|
|
496
470
|
* @param {ReadonlyMat4} b the second operand
|
|
497
471
|
* @returns {mat4} out
|
|
498
472
|
*/
|
|
499
|
-
|
|
500
473
|
function multiply(out, a, b) {
|
|
501
474
|
var a00 = a[0],
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
475
|
+
a01 = a[1],
|
|
476
|
+
a02 = a[2],
|
|
477
|
+
a03 = a[3];
|
|
505
478
|
var a10 = a[4],
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
479
|
+
a11 = a[5],
|
|
480
|
+
a12 = a[6],
|
|
481
|
+
a13 = a[7];
|
|
509
482
|
var a20 = a[8],
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
483
|
+
a21 = a[9],
|
|
484
|
+
a22 = a[10],
|
|
485
|
+
a23 = a[11];
|
|
513
486
|
var a30 = a[12],
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
487
|
+
a31 = a[13],
|
|
488
|
+
a32 = a[14],
|
|
489
|
+
a33 = a[15];
|
|
517
490
|
|
|
491
|
+
// Cache only the current line of the second matrix
|
|
518
492
|
var b0 = b[0],
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
493
|
+
b1 = b[1],
|
|
494
|
+
b2 = b[2],
|
|
495
|
+
b3 = b[3];
|
|
522
496
|
out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
|
|
523
497
|
out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
|
|
524
498
|
out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
|
|
@@ -549,6 +523,7 @@ function multiply(out, a, b) {
|
|
|
549
523
|
out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
|
|
550
524
|
return out;
|
|
551
525
|
}
|
|
526
|
+
|
|
552
527
|
/**
|
|
553
528
|
* Creates a matrix from a vector translation
|
|
554
529
|
* This is equivalent to (but much faster than):
|
|
@@ -560,7 +535,6 @@ function multiply(out, a, b) {
|
|
|
560
535
|
* @param {ReadonlyVec3} v Translation vector
|
|
561
536
|
* @returns {mat4} out
|
|
562
537
|
*/
|
|
563
|
-
|
|
564
538
|
function fromTranslation(out, v) {
|
|
565
539
|
out[0] = 1;
|
|
566
540
|
out[1] = 0;
|
|
@@ -580,6 +554,7 @@ function fromTranslation(out, v) {
|
|
|
580
554
|
out[15] = 1;
|
|
581
555
|
return out;
|
|
582
556
|
}
|
|
557
|
+
|
|
583
558
|
/**
|
|
584
559
|
* Creates a matrix from a vector scaling
|
|
585
560
|
* This is equivalent to (but much faster than):
|
|
@@ -591,7 +566,6 @@ function fromTranslation(out, v) {
|
|
|
591
566
|
* @param {ReadonlyVec3} v Scaling vector
|
|
592
567
|
* @returns {mat4} out
|
|
593
568
|
*/
|
|
594
|
-
|
|
595
569
|
function fromScaling(out, v) {
|
|
596
570
|
out[0] = v[0];
|
|
597
571
|
out[1] = 0;
|
|
@@ -611,6 +585,7 @@ function fromScaling(out, v) {
|
|
|
611
585
|
out[15] = 1;
|
|
612
586
|
return out;
|
|
613
587
|
}
|
|
588
|
+
|
|
614
589
|
/**
|
|
615
590
|
* Creates a matrix from a given angle around a given axis
|
|
616
591
|
* This is equivalent to (but much faster than):
|
|
@@ -623,26 +598,24 @@ function fromScaling(out, v) {
|
|
|
623
598
|
* @param {ReadonlyVec3} axis the axis to rotate around
|
|
624
599
|
* @returns {mat4} out
|
|
625
600
|
*/
|
|
626
|
-
|
|
627
601
|
function fromRotation(out, rad, axis) {
|
|
628
602
|
var x = axis[0],
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
var len = Math.
|
|
603
|
+
y = axis[1],
|
|
604
|
+
z = axis[2];
|
|
605
|
+
var len = Math.sqrt(x * x + y * y + z * z);
|
|
632
606
|
var s, c, t;
|
|
633
|
-
|
|
634
607
|
if (len < EPSILON) {
|
|
635
608
|
return null;
|
|
636
609
|
}
|
|
637
|
-
|
|
638
610
|
len = 1 / len;
|
|
639
611
|
x *= len;
|
|
640
612
|
y *= len;
|
|
641
613
|
z *= len;
|
|
642
614
|
s = Math.sin(rad);
|
|
643
615
|
c = Math.cos(rad);
|
|
644
|
-
t = 1 - c;
|
|
616
|
+
t = 1 - c;
|
|
645
617
|
|
|
618
|
+
// Perform rotation-specific matrix multiplication
|
|
646
619
|
out[0] = x * x * t + c;
|
|
647
620
|
out[1] = y * x * t + z * s;
|
|
648
621
|
out[2] = z * x * t - y * s;
|
|
@@ -661,6 +634,7 @@ function fromRotation(out, rad, axis) {
|
|
|
661
634
|
out[15] = 1;
|
|
662
635
|
return out;
|
|
663
636
|
}
|
|
637
|
+
|
|
664
638
|
/**
|
|
665
639
|
* Returns the translation vector component of a transformation
|
|
666
640
|
* matrix. If a matrix is built with fromRotationTranslation,
|
|
@@ -670,24 +644,23 @@ function fromRotation(out, rad, axis) {
|
|
|
670
644
|
* @param {ReadonlyMat4} mat Matrix to be decomposed (input)
|
|
671
645
|
* @return {vec3} out
|
|
672
646
|
*/
|
|
673
|
-
|
|
674
647
|
function getTranslation(out, mat) {
|
|
675
648
|
out[0] = mat[12];
|
|
676
649
|
out[1] = mat[13];
|
|
677
650
|
out[2] = mat[14];
|
|
678
651
|
return out;
|
|
679
652
|
}
|
|
653
|
+
|
|
680
654
|
/**
|
|
681
655
|
* Returns the scaling factor component of a transformation
|
|
682
656
|
* matrix. If a matrix is built with fromRotationTranslationScale
|
|
683
|
-
* with a normalized Quaternion
|
|
657
|
+
* with a normalized Quaternion parameter, the returned vector will be
|
|
684
658
|
* the same as the scaling vector
|
|
685
659
|
* originally supplied.
|
|
686
660
|
* @param {vec3} out Vector to receive scaling factor component
|
|
687
661
|
* @param {ReadonlyMat4} mat Matrix to be decomposed (input)
|
|
688
662
|
* @return {vec3} out
|
|
689
663
|
*/
|
|
690
|
-
|
|
691
664
|
function getScaling(out, mat) {
|
|
692
665
|
var m11 = mat[0];
|
|
693
666
|
var m12 = mat[1];
|
|
@@ -698,9 +671,9 @@ function getScaling(out, mat) {
|
|
|
698
671
|
var m31 = mat[8];
|
|
699
672
|
var m32 = mat[9];
|
|
700
673
|
var m33 = mat[10];
|
|
701
|
-
out[0] = Math.
|
|
702
|
-
out[1] = Math.
|
|
703
|
-
out[2] = Math.
|
|
674
|
+
out[0] = Math.sqrt(m11 * m11 + m12 * m12 + m13 * m13);
|
|
675
|
+
out[1] = Math.sqrt(m21 * m21 + m22 * m22 + m23 * m23);
|
|
676
|
+
out[2] = Math.sqrt(m31 * m31 + m32 * m32 + m33 * m33);
|
|
704
677
|
return out;
|
|
705
678
|
}
|
|
706
679
|
|
|
@@ -714,19 +687,17 @@ function getScaling(out, mat) {
|
|
|
714
687
|
*
|
|
715
688
|
* @returns {vec4} a new 4D vector
|
|
716
689
|
*/
|
|
717
|
-
|
|
718
690
|
function create$1() {
|
|
719
691
|
var out = new ARRAY_TYPE(4);
|
|
720
|
-
|
|
721
692
|
if (ARRAY_TYPE != Float32Array) {
|
|
722
693
|
out[0] = 0;
|
|
723
694
|
out[1] = 0;
|
|
724
695
|
out[2] = 0;
|
|
725
696
|
out[3] = 0;
|
|
726
697
|
}
|
|
727
|
-
|
|
728
698
|
return out;
|
|
729
699
|
}
|
|
700
|
+
|
|
730
701
|
/**
|
|
731
702
|
* Transforms the vec4 with a mat4.
|
|
732
703
|
*
|
|
@@ -735,18 +706,18 @@ function create$1() {
|
|
|
735
706
|
* @param {ReadonlyMat4} m matrix to transform with
|
|
736
707
|
* @returns {vec4} out
|
|
737
708
|
*/
|
|
738
|
-
|
|
739
709
|
function transformMat4(out, a, m) {
|
|
740
710
|
var x = a[0],
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
711
|
+
y = a[1],
|
|
712
|
+
z = a[2],
|
|
713
|
+
w = a[3];
|
|
744
714
|
out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
|
|
745
715
|
out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
|
|
746
716
|
out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
|
|
747
717
|
out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
|
|
748
718
|
return out;
|
|
749
719
|
}
|
|
720
|
+
|
|
750
721
|
/**
|
|
751
722
|
* Perform some operation over an array of vec4s.
|
|
752
723
|
*
|
|
@@ -759,26 +730,21 @@ function transformMat4(out, a, m) {
|
|
|
759
730
|
* @returns {Array} a
|
|
760
731
|
* @function
|
|
761
732
|
*/
|
|
762
|
-
|
|
763
733
|
(function () {
|
|
764
734
|
var vec = create$1();
|
|
765
735
|
return function (a, stride, offset, count, fn, arg) {
|
|
766
736
|
var i, l;
|
|
767
|
-
|
|
768
737
|
if (!stride) {
|
|
769
738
|
stride = 4;
|
|
770
739
|
}
|
|
771
|
-
|
|
772
740
|
if (!offset) {
|
|
773
741
|
offset = 0;
|
|
774
742
|
}
|
|
775
|
-
|
|
776
743
|
if (count) {
|
|
777
744
|
l = Math.min(count * stride + offset, a.length);
|
|
778
745
|
} else {
|
|
779
746
|
l = a.length;
|
|
780
747
|
}
|
|
781
|
-
|
|
782
748
|
for (i = offset; i < l; i += stride) {
|
|
783
749
|
vec[0] = a[i];
|
|
784
750
|
vec[1] = a[i + 1];
|
|
@@ -790,7 +756,6 @@ function transformMat4(out, a, m) {
|
|
|
790
756
|
a[i + 2] = vec[2];
|
|
791
757
|
a[i + 3] = vec[3];
|
|
792
758
|
}
|
|
793
|
-
|
|
794
759
|
return a;
|
|
795
760
|
};
|
|
796
761
|
})();
|
|
@@ -805,36 +770,29 @@ function transformMat4(out, a, m) {
|
|
|
805
770
|
*
|
|
806
771
|
* @returns {vec2} a new 2D vector
|
|
807
772
|
*/
|
|
808
|
-
|
|
809
773
|
function create() {
|
|
810
774
|
var out = new ARRAY_TYPE(2);
|
|
811
|
-
|
|
812
775
|
if (ARRAY_TYPE != Float32Array) {
|
|
813
776
|
out[0] = 0;
|
|
814
777
|
out[1] = 0;
|
|
815
778
|
}
|
|
816
|
-
|
|
817
779
|
return out;
|
|
818
780
|
}
|
|
781
|
+
|
|
819
782
|
/**
|
|
820
|
-
* Get the angle between two 2D vectors
|
|
783
|
+
* Get the smallest angle between two 2D vectors
|
|
821
784
|
* @param {ReadonlyVec2} a The first operand
|
|
822
785
|
* @param {ReadonlyVec2} b The second operand
|
|
823
786
|
* @returns {Number} The angle in radians
|
|
824
787
|
*/
|
|
825
|
-
|
|
826
788
|
function angle(a, b) {
|
|
827
|
-
var
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
mag = Math.sqrt(x1 * x1 + y1 * y1) * Math.sqrt(x2 * x2 + y2 * y2),
|
|
833
|
-
// mag &&.. short circuits if mag == 0
|
|
834
|
-
cosine = mag && (x1 * x2 + y1 * y2) / mag; // Math.min(Math.max(cosine, -1), 1) clamps the cosine between -1 and 1
|
|
835
|
-
|
|
836
|
-
return Math.acos(Math.min(Math.max(cosine, -1), 1));
|
|
789
|
+
var ax = a[0],
|
|
790
|
+
ay = a[1],
|
|
791
|
+
bx = b[0],
|
|
792
|
+
by = b[1];
|
|
793
|
+
return Math.abs(Math.atan2(ay * bx - ax * by, ax * bx + ay * by));
|
|
837
794
|
}
|
|
795
|
+
|
|
838
796
|
/**
|
|
839
797
|
* Perform some operation over an array of vec2s.
|
|
840
798
|
*
|
|
@@ -847,26 +805,21 @@ function angle(a, b) {
|
|
|
847
805
|
* @returns {Array} a
|
|
848
806
|
* @function
|
|
849
807
|
*/
|
|
850
|
-
|
|
851
808
|
(function () {
|
|
852
809
|
var vec = create();
|
|
853
810
|
return function (a, stride, offset, count, fn, arg) {
|
|
854
811
|
var i, l;
|
|
855
|
-
|
|
856
812
|
if (!stride) {
|
|
857
813
|
stride = 2;
|
|
858
814
|
}
|
|
859
|
-
|
|
860
815
|
if (!offset) {
|
|
861
816
|
offset = 0;
|
|
862
817
|
}
|
|
863
|
-
|
|
864
818
|
if (count) {
|
|
865
819
|
l = Math.min(count * stride + offset, a.length);
|
|
866
820
|
} else {
|
|
867
821
|
l = a.length;
|
|
868
822
|
}
|
|
869
|
-
|
|
870
823
|
for (i = offset; i < l; i += stride) {
|
|
871
824
|
vec[0] = a[i];
|
|
872
825
|
vec[1] = a[i + 1];
|
|
@@ -874,7 +827,6 @@ function angle(a, b) {
|
|
|
874
827
|
a[i] = vec[0];
|
|
875
828
|
a[i + 1] = vec[1];
|
|
876
829
|
}
|
|
877
|
-
|
|
878
830
|
return a;
|
|
879
831
|
};
|
|
880
832
|
})();
|
|
@@ -1467,10 +1419,10 @@ function earcut(data, holeIndices, dim = 2) {
|
|
|
1467
1419
|
|
|
1468
1420
|
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
|
|
1469
1421
|
if (data.length > 80 * dim) {
|
|
1470
|
-
minX =
|
|
1471
|
-
minY =
|
|
1472
|
-
let maxX =
|
|
1473
|
-
let maxY =
|
|
1422
|
+
minX = data[0];
|
|
1423
|
+
minY = data[1];
|
|
1424
|
+
let maxX = minX;
|
|
1425
|
+
let maxY = minY;
|
|
1474
1426
|
|
|
1475
1427
|
for (let i = dim; i < outerLen; i += dim) {
|
|
1476
1428
|
const x = data[i];
|
|
@@ -1812,7 +1764,7 @@ function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) {
|
|
|
1812
1764
|
|
|
1813
1765
|
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
|
|
1814
1766
|
function isValidDiagonal(a, b) {
|
|
1815
|
-
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && //
|
|
1767
|
+
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges
|
|
1816
1768
|
(locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
|
|
1817
1769
|
(area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
|
|
1818
1770
|
equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case
|
|
@@ -2426,121 +2378,327 @@ const createLine = (regl, { projection = I, model = I, view = I, points = [], co
|
|
|
2426
2378
|
};
|
|
2427
2379
|
};
|
|
2428
2380
|
|
|
2429
|
-
|
|
2430
|
-
* KDBush - A fast static index for 2D points
|
|
2431
|
-
* @license ISC License
|
|
2432
|
-
* @copyright Vladimir Agafonkin 2018
|
|
2433
|
-
* @version 4.0.2
|
|
2434
|
-
* @see https://github.com/mourner/kdbush/
|
|
2435
|
-
*/
|
|
2436
|
-
var createKDBushClass = () => {
|
|
2437
|
-
const ARRAY_TYPES = [
|
|
2438
|
-
Int8Array,
|
|
2439
|
-
Uint8Array,
|
|
2440
|
-
Uint8ClampedArray,
|
|
2441
|
-
Int16Array,
|
|
2442
|
-
Uint16Array,
|
|
2443
|
-
Int32Array,
|
|
2444
|
-
Uint32Array,
|
|
2445
|
-
Float32Array,
|
|
2446
|
-
Float64Array,
|
|
2447
|
-
];
|
|
2381
|
+
var version = "1.16.0-hemato.0";
|
|
2448
2382
|
|
|
2449
|
-
|
|
2383
|
+
const FRAGMENT_SHADER$2 = `
|
|
2384
|
+
precision mediump float;
|
|
2450
2385
|
|
|
2451
|
-
|
|
2452
|
-
const HEADER_SIZE = 8;
|
|
2386
|
+
uniform sampler2D texture;
|
|
2453
2387
|
|
|
2454
|
-
|
|
2455
|
-
/**
|
|
2456
|
-
* Creates an index from raw `ArrayBuffer` data.
|
|
2457
|
-
* @param {ArrayBuffer} data
|
|
2458
|
-
*/
|
|
2459
|
-
static from(data) {
|
|
2460
|
-
if (!(data instanceof ArrayBuffer)) {
|
|
2461
|
-
throw new Error('Data must be an instance of ArrayBuffer.');
|
|
2462
|
-
}
|
|
2463
|
-
const [magic, versionAndType] = new Uint8Array(data, 0, 2);
|
|
2464
|
-
if (magic !== 0xdb) {
|
|
2465
|
-
throw new Error('Data does not appear to be in a KDBush format.');
|
|
2466
|
-
}
|
|
2467
|
-
const version = versionAndType >> 4;
|
|
2468
|
-
if (version !== VERSION) {
|
|
2469
|
-
throw new Error(`Got v${version} data when expected v${VERSION}.`);
|
|
2470
|
-
}
|
|
2471
|
-
const ArrayType = ARRAY_TYPES[versionAndType & 0x0f];
|
|
2472
|
-
if (!ArrayType) {
|
|
2473
|
-
throw new Error('Unrecognized array type.');
|
|
2474
|
-
}
|
|
2475
|
-
const [nodeSize] = new Uint16Array(data, 2, 1);
|
|
2476
|
-
const [numItems] = new Uint32Array(data, 4, 1);
|
|
2388
|
+
varying vec2 uv;
|
|
2477
2389
|
|
|
2478
|
-
|
|
2479
|
-
|
|
2390
|
+
void main () {
|
|
2391
|
+
gl_FragColor = texture2D(texture, uv);
|
|
2392
|
+
}
|
|
2393
|
+
`;
|
|
2480
2394
|
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
* @param {number} numItems
|
|
2484
|
-
* @param {number} [nodeSize=64] Size of the KD-tree node (64 by default).
|
|
2485
|
-
* @param {TypedArrayConstructor} [ArrayType=Float64Array] The array type used for coordinates storage (`Float64Array` by default).
|
|
2486
|
-
* @param {ArrayBuffer} [data] (For internal use only)
|
|
2487
|
-
*/
|
|
2488
|
-
constructor(numItems, nodeSize = 64, ArrayType = Float64Array, data) {
|
|
2489
|
-
if (isNaN(numItems) || numItems < 0)
|
|
2490
|
-
throw new Error(`Unexpected numItems value: ${numItems}.`);
|
|
2395
|
+
const VERTEX_SHADER = `
|
|
2396
|
+
precision mediump float;
|
|
2491
2397
|
|
|
2492
|
-
|
|
2493
|
-
this.nodeSize = Math.min(Math.max(+nodeSize, 2), 65535);
|
|
2494
|
-
this.ArrayType = ArrayType;
|
|
2495
|
-
this.IndexArrayType = numItems < 65536 ? Uint16Array : Uint32Array;
|
|
2398
|
+
uniform mat4 modelViewProjection;
|
|
2496
2399
|
|
|
2497
|
-
|
|
2498
|
-
const coordsByteSize = numItems * 2 * this.ArrayType.BYTES_PER_ELEMENT;
|
|
2499
|
-
const idsByteSize = numItems * this.IndexArrayType.BYTES_PER_ELEMENT;
|
|
2500
|
-
const padCoords = (8 - (idsByteSize % 8)) % 8;
|
|
2400
|
+
attribute vec2 position;
|
|
2501
2401
|
|
|
2502
|
-
|
|
2503
|
-
throw new Error(`Unexpected typed array class: ${ArrayType}.`);
|
|
2504
|
-
}
|
|
2402
|
+
varying vec2 uv;
|
|
2505
2403
|
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
this.data,
|
|
2512
|
-
HEADER_SIZE + idsByteSize + padCoords,
|
|
2513
|
-
numItems * 2
|
|
2514
|
-
);
|
|
2515
|
-
this._pos = numItems * 2;
|
|
2516
|
-
this._finished = true;
|
|
2517
|
-
} else {
|
|
2518
|
-
// initialize a new index
|
|
2519
|
-
this.data = new ArrayBuffer(
|
|
2520
|
-
HEADER_SIZE + coordsByteSize + idsByteSize + padCoords
|
|
2521
|
-
);
|
|
2522
|
-
this.ids = new this.IndexArrayType(this.data, HEADER_SIZE, numItems);
|
|
2523
|
-
this.coords = new this.ArrayType(
|
|
2524
|
-
this.data,
|
|
2525
|
-
HEADER_SIZE + idsByteSize + padCoords,
|
|
2526
|
-
numItems * 2
|
|
2527
|
-
);
|
|
2528
|
-
this._pos = 0;
|
|
2529
|
-
this._finished = false;
|
|
2404
|
+
void main () {
|
|
2405
|
+
uv = position;
|
|
2406
|
+
gl_Position = modelViewProjection * vec4(-1.0 + 2.0 * uv.x, 1.0 - 2.0 * uv.y, 0, 1);
|
|
2407
|
+
}
|
|
2408
|
+
`;
|
|
2530
2409
|
|
|
2531
|
-
|
|
2532
|
-
new Uint8Array(this.data, 0, 2).set([
|
|
2533
|
-
0xdb,
|
|
2534
|
-
(VERSION << 4) + arrayTypeIndex,
|
|
2535
|
-
]);
|
|
2536
|
-
new Uint16Array(this.data, 2, 1)[0] = nodeSize;
|
|
2537
|
-
new Uint32Array(this.data, 4, 1)[0] = numItems;
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2410
|
+
const AUTO = 'auto';
|
|
2540
2411
|
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2412
|
+
const COLOR_NORMAL_IDX = 0;
|
|
2413
|
+
const COLOR_ACTIVE_IDX = 1;
|
|
2414
|
+
const COLOR_HOVER_IDX = 2;
|
|
2415
|
+
const COLOR_BG_IDX = 3;
|
|
2416
|
+
const COLOR_NUM_STATES = 4;
|
|
2417
|
+
const FLOAT_BYTES = Float32Array.BYTES_PER_ELEMENT;
|
|
2418
|
+
const GL_EXTENSIONS = [
|
|
2419
|
+
'OES_texture_float',
|
|
2420
|
+
'OES_element_index_uint',
|
|
2421
|
+
'WEBGL_color_buffer_float',
|
|
2422
|
+
'EXT_float_blend',
|
|
2423
|
+
];
|
|
2424
|
+
const CLEAR_OPTIONS = {
|
|
2425
|
+
color: [0, 0, 0, 0], // Transparent background color
|
|
2426
|
+
depth: 1,
|
|
2427
|
+
};
|
|
2428
|
+
|
|
2429
|
+
const MOUSE_MODE_PANZOOM = 'panZoom';
|
|
2430
|
+
const MOUSE_MODE_LASSO = 'lasso';
|
|
2431
|
+
const MOUSE_MODE_ROTATE = 'rotate';
|
|
2432
|
+
const MOUSE_MODES = [
|
|
2433
|
+
MOUSE_MODE_PANZOOM,
|
|
2434
|
+
MOUSE_MODE_LASSO,
|
|
2435
|
+
MOUSE_MODE_ROTATE,
|
|
2436
|
+
];
|
|
2437
|
+
const DEFAULT_MOUSE_MODE = MOUSE_MODE_PANZOOM;
|
|
2438
|
+
|
|
2439
|
+
// Easing
|
|
2440
|
+
const EASING_FNS = {
|
|
2441
|
+
cubicIn,
|
|
2442
|
+
cubicInOut,
|
|
2443
|
+
cubicOut,
|
|
2444
|
+
linear,
|
|
2445
|
+
quadIn,
|
|
2446
|
+
quadInOut,
|
|
2447
|
+
quadOut,
|
|
2448
|
+
};
|
|
2449
|
+
const DEFAULT_EASING = cubicInOut;
|
|
2450
|
+
|
|
2451
|
+
const CONTINUOUS = 'continuous';
|
|
2452
|
+
const CATEGORICAL = 'categorical';
|
|
2453
|
+
const VALUE_ZW_DATA_TYPES = [CONTINUOUS, CATEGORICAL];
|
|
2454
|
+
|
|
2455
|
+
// Default lasso
|
|
2456
|
+
const LASSO_CLEAR_ON_DESELECT = 'deselect';
|
|
2457
|
+
const LASSO_CLEAR_ON_END = 'lassoEnd';
|
|
2458
|
+
const LASSO_CLEAR_EVENTS = [LASSO_CLEAR_ON_DESELECT, LASSO_CLEAR_ON_END];
|
|
2459
|
+
const LASSO_BRUSH_MIN_MIN_DIST = 3;
|
|
2460
|
+
const DEFAULT_LASSO_COLOR = [0, 0.666666667, 1, 1];
|
|
2461
|
+
const DEFAULT_LASSO_LINE_WIDTH = 2;
|
|
2462
|
+
const DEFAULT_LASSO_INITIATOR = false;
|
|
2463
|
+
const DEFAULT_LASSO_MIN_DELAY$1 = 10;
|
|
2464
|
+
const DEFAULT_LASSO_MIN_DIST$1 = 3;
|
|
2465
|
+
const DEFAULT_LASSO_CLEAR_EVENT = LASSO_CLEAR_ON_END;
|
|
2466
|
+
const DEFAULT_LASSO_ON_LONG_PRESS = false;
|
|
2467
|
+
const DEFAULT_LASSO_LONG_PRESS_TIME = 750;
|
|
2468
|
+
const DEFAULT_LASSO_LONG_PRESS_AFTER_EFFECT_TIME = 500;
|
|
2469
|
+
const DEFAULT_LASSO_LONG_PRESS_EFFECT_DELAY = 100;
|
|
2470
|
+
const DEFAULT_LASSO_LONG_PRESS_REVERT_EFFECT_TIME = 250;
|
|
2471
|
+
const DEFAULT_LASSO_BRUSH_SIZE = 24;
|
|
2472
|
+
const DEFAULT_LASSO_MODE = null;
|
|
2473
|
+
|
|
2474
|
+
// Key mapping
|
|
2475
|
+
const KEY_ACTION_INTERSECT = 'intersect';
|
|
2476
|
+
const KEY_ACTION_LASSO = 'lasso';
|
|
2477
|
+
const KEY_ACTION_ROTATE = 'rotate';
|
|
2478
|
+
const KEY_ACTION_MERGE = 'merge';
|
|
2479
|
+
const KEY_ACTION_REMOVE = 'remove';
|
|
2480
|
+
const KEY_ACTIONS = [
|
|
2481
|
+
KEY_ACTION_LASSO,
|
|
2482
|
+
KEY_ACTION_ROTATE,
|
|
2483
|
+
KEY_ACTION_INTERSECT,
|
|
2484
|
+
KEY_ACTION_MERGE,
|
|
2485
|
+
KEY_ACTION_REMOVE,
|
|
2486
|
+
];
|
|
2487
|
+
const KEY_ALT = 'alt';
|
|
2488
|
+
const KEY_CMD = 'cmd';
|
|
2489
|
+
const KEY_CTRL = 'ctrl';
|
|
2490
|
+
const KEY_META = 'meta';
|
|
2491
|
+
const KEY_SHIFT = 'shift';
|
|
2492
|
+
const KEYS = [KEY_ALT, KEY_CMD, KEY_CTRL, KEY_META, KEY_SHIFT];
|
|
2493
|
+
const DEFAULT_ACTION_KEY_MAP = {
|
|
2494
|
+
[KEY_ACTION_REMOVE]: KEY_ALT,
|
|
2495
|
+
[KEY_ACTION_ROTATE]: KEY_ALT,
|
|
2496
|
+
[KEY_ACTION_LASSO]: KEY_SHIFT,
|
|
2497
|
+
[KEY_ACTION_MERGE]: KEY_CMD,
|
|
2498
|
+
};
|
|
2499
|
+
|
|
2500
|
+
// Default attribute
|
|
2501
|
+
const DEFAULT_DATA_ASPECT_RATIO = 1;
|
|
2502
|
+
const DEFAULT_WIDTH = AUTO;
|
|
2503
|
+
const DEFAULT_HEIGHT = AUTO;
|
|
2504
|
+
const DEFAULT_GAMMA = 1;
|
|
2505
|
+
|
|
2506
|
+
// Default styles
|
|
2507
|
+
const MIN_POINT_SIZE = 1;
|
|
2508
|
+
const DEFAULT_POINT_SCALE_MODE = 'asinh';
|
|
2509
|
+
const DEFAULT_POINT_SIZE = 6;
|
|
2510
|
+
const DEFAULT_POINT_SIZE_SELECTED = 2;
|
|
2511
|
+
const DEFAULT_POINT_OUTLINE_WIDTH = 2;
|
|
2512
|
+
const DEFAULT_SIZE_BY = null;
|
|
2513
|
+
const DEFAULT_POINT_ORDER = null;
|
|
2514
|
+
const DEFAULT_POINT_CONNECTION_SIZE = 2;
|
|
2515
|
+
const DEFAULT_POINT_CONNECTION_SIZE_ACTIVE = 2;
|
|
2516
|
+
const DEFAULT_POINT_CONNECTION_SIZE_BY = null;
|
|
2517
|
+
const DEFAULT_POINT_CONNECTION_OPACITY = null;
|
|
2518
|
+
const DEFAULT_POINT_CONNECTION_OPACITY_BY = null;
|
|
2519
|
+
const DEFAULT_POINT_CONNECTION_OPACITY_ACTIVE = 0.66;
|
|
2520
|
+
const DEFAULT_OPACITY = 1;
|
|
2521
|
+
const DEFAULT_OPACITY_BY = null;
|
|
2522
|
+
const DEFAULT_OPACITY_BY_DENSITY_FILL = 0.15;
|
|
2523
|
+
const DEFAULT_OPACITY_BY_DENSITY_DEBOUNCE_TIME = 25;
|
|
2524
|
+
const DEFAULT_OPACITY_INACTIVE_MAX = 1;
|
|
2525
|
+
const DEFAULT_OPACITY_INACTIVE_SCALE = 1;
|
|
2526
|
+
const DEFAULT_COLOR_BY = null;
|
|
2527
|
+
const DEFAULT_COLOR_NORMAL = [0.66, 0.66, 0.66, DEFAULT_OPACITY];
|
|
2528
|
+
const DEFAULT_COLOR_ACTIVE = [0, 0.55, 1, 1];
|
|
2529
|
+
const DEFAULT_COLOR_HOVER = [1, 1, 1, 1];
|
|
2530
|
+
const DEFAULT_COLOR_BG = [0, 0, 0, 1];
|
|
2531
|
+
const DEFAULT_POINT_CONNECTION_COLOR_BY = null;
|
|
2532
|
+
const DEFAULT_POINT_CONNECTION_COLOR_NORMAL = [0.66, 0.66, 0.66, 0.2];
|
|
2533
|
+
const DEFAULT_POINT_CONNECTION_COLOR_ACTIVE = [0, 0.55, 1, 1];
|
|
2534
|
+
const DEFAULT_POINT_CONNECTION_COLOR_HOVER = [1, 1, 1, 1];
|
|
2535
|
+
|
|
2536
|
+
// Annotations
|
|
2537
|
+
const DEFAULT_ANNOTATION_LINE_COLOR = [1, 1, 1, 0.5];
|
|
2538
|
+
const DEFAULT_ANNOTATION_LINE_WIDTH = 1;
|
|
2539
|
+
const DEFAULT_ANNOTATION_HVLINE_LIMIT = 1000;
|
|
2540
|
+
|
|
2541
|
+
// Default view
|
|
2542
|
+
const DEFAULT_TARGET = [0, 0];
|
|
2543
|
+
const DEFAULT_DISTANCE = 1;
|
|
2544
|
+
const DEFAULT_ROTATION = 0;
|
|
2545
|
+
// biome-ignore format: the array should not be formatted
|
|
2546
|
+
const DEFAULT_VIEW = new Float32Array([
|
|
2547
|
+
1, 0, 0, 0,
|
|
2548
|
+
0, 1, 0, 0,
|
|
2549
|
+
0, 0, 1, 0,
|
|
2550
|
+
0, 0, 0, 1,
|
|
2551
|
+
]);
|
|
2552
|
+
|
|
2553
|
+
// Error codes
|
|
2554
|
+
const IMAGE_LOAD_ERROR = 'IMAGE_LOAD_ERROR';
|
|
2555
|
+
|
|
2556
|
+
// Default misc
|
|
2557
|
+
const DEFAULT_BACKGROUND_IMAGE = null;
|
|
2558
|
+
const DEFAULT_SHOW_RETICLE = false;
|
|
2559
|
+
const DEFAULT_RETICLE_COLOR = [1, 1, 1, 0.5];
|
|
2560
|
+
const DEFAULT_DESELECT_ON_DBL_CLICK = true;
|
|
2561
|
+
const DEFAULT_DESELECT_ON_ESCAPE = true;
|
|
2562
|
+
const DEFAULT_SHOW_POINT_CONNECTIONS = false;
|
|
2563
|
+
const DEFAULT_POINT_CONNECTION_MAX_INT_POINTS_PER_SEGMENT = 100;
|
|
2564
|
+
const DEFAULT_POINT_CONNECTION_INT_POINTS_TOLERANCE = 1 / 500;
|
|
2565
|
+
const DEFAULT_POINT_SIZE_MOUSE_DETECTION = 'auto';
|
|
2566
|
+
const DEFAULT_PERFORMANCE_MODE = false;
|
|
2567
|
+
const SINGLE_CLICK_DELAY = 200;
|
|
2568
|
+
const LONG_CLICK_TIME = 500;
|
|
2569
|
+
const Z_NAMES = new Set(['z', 'valueZ', 'valueA', 'value1', 'category']);
|
|
2570
|
+
const W_NAMES = new Set(['w', 'valueW', 'valueB', 'value2', 'value']);
|
|
2571
|
+
const DEFAULT_IMAGE_LOAD_TIMEOUT = 15000;
|
|
2572
|
+
const DEFAULT_SPATIAL_INDEX_USE_WORKER = undefined;
|
|
2573
|
+
const DEFAULT_CAMERA_IS_FIXED = false;
|
|
2574
|
+
const DEFAULT_ANTI_ALIASING = 0.5;
|
|
2575
|
+
const DEFAULT_PIXEL_ALIGNED = false;
|
|
2576
|
+
const DEFAULT_LASSO_TYPE$1 = 'lasso';
|
|
2577
|
+
const SKIP_DEPRECATION_VALUE_TRANSLATION = Symbol(
|
|
2578
|
+
'SKIP_DEPRECATION_VALUE_TRANSLATION',
|
|
2579
|
+
);
|
|
2580
|
+
|
|
2581
|
+
// Error messages
|
|
2582
|
+
const ERROR_POINTS_NOT_DRAWN = 'Points have not been drawn';
|
|
2583
|
+
const ERROR_INSTANCE_IS_DESTROYED = 'The instance was already destroyed';
|
|
2584
|
+
const ERROR_IS_DRAWING =
|
|
2585
|
+
'Ignoring draw call as the previous draw call has not yet finished. To avoid this warning `await` the draw call.';
|
|
2586
|
+
|
|
2587
|
+
/**
|
|
2588
|
+
* KDBush - A fast static index for 2D points
|
|
2589
|
+
* @license ISC License
|
|
2590
|
+
* @copyright Vladimir Agafonkin 2018
|
|
2591
|
+
* @version 4.0.2
|
|
2592
|
+
* @see https://github.com/mourner/kdbush/
|
|
2593
|
+
*/
|
|
2594
|
+
var createKDBushClass = () => {
|
|
2595
|
+
const ARRAY_TYPES = [
|
|
2596
|
+
Int8Array,
|
|
2597
|
+
Uint8Array,
|
|
2598
|
+
Uint8ClampedArray,
|
|
2599
|
+
Int16Array,
|
|
2600
|
+
Uint16Array,
|
|
2601
|
+
Int32Array,
|
|
2602
|
+
Uint32Array,
|
|
2603
|
+
Float32Array,
|
|
2604
|
+
Float64Array,
|
|
2605
|
+
];
|
|
2606
|
+
|
|
2607
|
+
/** @typedef {Int8ArrayConstructor | Uint8ArrayConstructor | Uint8ClampedArrayConstructor | Int16ArrayConstructor | Uint16ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor} TypedArrayConstructor */
|
|
2608
|
+
|
|
2609
|
+
const VERSION = 1; // serialized format version
|
|
2610
|
+
const HEADER_SIZE = 8;
|
|
2611
|
+
|
|
2612
|
+
class KDBush {
|
|
2613
|
+
/**
|
|
2614
|
+
* Creates an index from raw `ArrayBuffer` data.
|
|
2615
|
+
* @param {ArrayBuffer} data
|
|
2616
|
+
*/
|
|
2617
|
+
static from(data) {
|
|
2618
|
+
if (!(data instanceof ArrayBuffer)) {
|
|
2619
|
+
throw new Error('Data must be an instance of ArrayBuffer.');
|
|
2620
|
+
}
|
|
2621
|
+
const [magic, versionAndType] = new Uint8Array(data, 0, 2);
|
|
2622
|
+
if (magic !== 0xdb) {
|
|
2623
|
+
throw new Error('Data does not appear to be in a KDBush format.');
|
|
2624
|
+
}
|
|
2625
|
+
const version = versionAndType >> 4;
|
|
2626
|
+
if (version !== VERSION) {
|
|
2627
|
+
throw new Error(`Got v${version} data when expected v${VERSION}.`);
|
|
2628
|
+
}
|
|
2629
|
+
const ArrayType = ARRAY_TYPES[versionAndType & 0x0f];
|
|
2630
|
+
if (!ArrayType) {
|
|
2631
|
+
throw new Error('Unrecognized array type.');
|
|
2632
|
+
}
|
|
2633
|
+
const [nodeSize] = new Uint16Array(data, 2, 1);
|
|
2634
|
+
const [numItems] = new Uint32Array(data, 4, 1);
|
|
2635
|
+
|
|
2636
|
+
return new KDBush(numItems, nodeSize, ArrayType, data);
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
/**
|
|
2640
|
+
* Creates an index that will hold a given number of items.
|
|
2641
|
+
* @param {number} numItems
|
|
2642
|
+
* @param {number} [nodeSize=64] Size of the KD-tree node (64 by default).
|
|
2643
|
+
* @param {TypedArrayConstructor} [ArrayType=Float64Array] The array type used for coordinates storage (`Float64Array` by default).
|
|
2644
|
+
* @param {ArrayBuffer} [data] (For internal use only)
|
|
2645
|
+
*/
|
|
2646
|
+
constructor(numItems, nodeSize = 64, ArrayType = Float64Array, data) {
|
|
2647
|
+
if (isNaN(numItems) || numItems < 0)
|
|
2648
|
+
throw new Error(`Unexpected numItems value: ${numItems}.`);
|
|
2649
|
+
|
|
2650
|
+
this.numItems = +numItems;
|
|
2651
|
+
this.nodeSize = Math.min(Math.max(+nodeSize, 2), 65535);
|
|
2652
|
+
this.ArrayType = ArrayType;
|
|
2653
|
+
this.IndexArrayType = numItems < 65536 ? Uint16Array : Uint32Array;
|
|
2654
|
+
|
|
2655
|
+
const arrayTypeIndex = ARRAY_TYPES.indexOf(this.ArrayType);
|
|
2656
|
+
const coordsByteSize = numItems * 2 * this.ArrayType.BYTES_PER_ELEMENT;
|
|
2657
|
+
const idsByteSize = numItems * this.IndexArrayType.BYTES_PER_ELEMENT;
|
|
2658
|
+
const padCoords = (8 - (idsByteSize % 8)) % 8;
|
|
2659
|
+
|
|
2660
|
+
if (arrayTypeIndex < 0) {
|
|
2661
|
+
throw new Error(`Unexpected typed array class: ${ArrayType}.`);
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
if (data && data instanceof ArrayBuffer) {
|
|
2665
|
+
// reconstruct an index from a buffer
|
|
2666
|
+
this.data = data;
|
|
2667
|
+
this.ids = new this.IndexArrayType(this.data, HEADER_SIZE, numItems);
|
|
2668
|
+
this.coords = new this.ArrayType(
|
|
2669
|
+
this.data,
|
|
2670
|
+
HEADER_SIZE + idsByteSize + padCoords,
|
|
2671
|
+
numItems * 2
|
|
2672
|
+
);
|
|
2673
|
+
this._pos = numItems * 2;
|
|
2674
|
+
this._finished = true;
|
|
2675
|
+
} else {
|
|
2676
|
+
// initialize a new index
|
|
2677
|
+
this.data = new ArrayBuffer(
|
|
2678
|
+
HEADER_SIZE + coordsByteSize + idsByteSize + padCoords
|
|
2679
|
+
);
|
|
2680
|
+
this.ids = new this.IndexArrayType(this.data, HEADER_SIZE, numItems);
|
|
2681
|
+
this.coords = new this.ArrayType(
|
|
2682
|
+
this.data,
|
|
2683
|
+
HEADER_SIZE + idsByteSize + padCoords,
|
|
2684
|
+
numItems * 2
|
|
2685
|
+
);
|
|
2686
|
+
this._pos = 0;
|
|
2687
|
+
this._finished = false;
|
|
2688
|
+
|
|
2689
|
+
// set header
|
|
2690
|
+
new Uint8Array(this.data, 0, 2).set([
|
|
2691
|
+
0xdb,
|
|
2692
|
+
(VERSION << 4) + arrayTypeIndex,
|
|
2693
|
+
]);
|
|
2694
|
+
new Uint16Array(this.data, 2, 1)[0] = nodeSize;
|
|
2695
|
+
new Uint32Array(this.data, 4, 1)[0] = numItems;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
/**
|
|
2700
|
+
* Add a point to the index.
|
|
2701
|
+
* @param {number} x
|
|
2544
2702
|
* @param {number} y
|
|
2545
2703
|
* @returns {number} An incremental index associated with the added item (starting from `0`).
|
|
2546
2704
|
*/
|
|
@@ -2804,7 +2962,6 @@ var workerFn = () => {
|
|
|
2804
2962
|
self.postMessage({ error: new Error('Invalid point data') });
|
|
2805
2963
|
}
|
|
2806
2964
|
|
|
2807
|
-
// biome-ignore lint/correctness/noUndeclaredVariables: KDBush is made available during compilation
|
|
2808
2965
|
const index = new KDBush(points.length, event.data.nodeSize);
|
|
2809
2966
|
|
|
2810
2967
|
for (const [x, y] of points) {
|
|
@@ -2817,9 +2974,6 @@ var workerFn = () => {
|
|
|
2817
2974
|
});
|
|
2818
2975
|
};
|
|
2819
2976
|
|
|
2820
|
-
// biome-ignore lint/style/useNamingConvention: KDBush is a library name
|
|
2821
|
-
|
|
2822
|
-
// biome-ignore lint/style/useNamingConvention: KDBush is a library name
|
|
2823
2977
|
const KDBush$1 = createKDBushClass();
|
|
2824
2978
|
const WORKER_THRESHOLD = 1000000;
|
|
2825
2979
|
|
|
@@ -2827,7 +2981,6 @@ const createWorker = (fn) => {
|
|
|
2827
2981
|
const kdbushStr = createKDBushClass.toString();
|
|
2828
2982
|
const fnStr = fn.toString();
|
|
2829
2983
|
const workerStr =
|
|
2830
|
-
// biome-ignore lint/style/useTemplate: Prefer one assignment per line
|
|
2831
2984
|
`const createKDBushClass = ${kdbushStr};` +
|
|
2832
2985
|
'KDBush = createKDBushClass();' +
|
|
2833
2986
|
`const createWorker = ${fnStr};` +
|
|
@@ -2883,190 +3036,16 @@ const createKdbush = (
|
|
|
2883
3036
|
}
|
|
2884
3037
|
});
|
|
2885
3038
|
|
|
3039
|
+
const kdbushFrom = (buffer) => KDBush$1.from(buffer);
|
|
3040
|
+
|
|
2886
3041
|
const DEFAULT_LASSO_START_INITIATOR_SHOW = true;
|
|
2887
|
-
const DEFAULT_LASSO_MIN_DELAY
|
|
2888
|
-
const DEFAULT_LASSO_MIN_DIST
|
|
2889
|
-
const DEFAULT_LASSO_TYPE
|
|
3042
|
+
const DEFAULT_LASSO_MIN_DELAY = 8;
|
|
3043
|
+
const DEFAULT_LASSO_MIN_DIST = 2;
|
|
3044
|
+
const DEFAULT_LASSO_TYPE = 'freeform';
|
|
2890
3045
|
const DEFAULT_BRUSH_SIZE = 24;
|
|
2891
3046
|
const LASSO_SHOW_START_INITIATOR_TIME = 2500;
|
|
2892
3047
|
const LASSO_HIDE_START_INITIATOR_TIME = 250;
|
|
2893
3048
|
|
|
2894
|
-
const AUTO = 'auto';
|
|
2895
|
-
|
|
2896
|
-
const COLOR_NORMAL_IDX = 0;
|
|
2897
|
-
const COLOR_ACTIVE_IDX = 1;
|
|
2898
|
-
const COLOR_HOVER_IDX = 2;
|
|
2899
|
-
const COLOR_BG_IDX = 3;
|
|
2900
|
-
const COLOR_NUM_STATES = 4;
|
|
2901
|
-
const FLOAT_BYTES = Float32Array.BYTES_PER_ELEMENT;
|
|
2902
|
-
const GL_EXTENSIONS = [
|
|
2903
|
-
'OES_texture_float',
|
|
2904
|
-
'OES_element_index_uint',
|
|
2905
|
-
'WEBGL_color_buffer_float',
|
|
2906
|
-
'EXT_float_blend',
|
|
2907
|
-
];
|
|
2908
|
-
const CLEAR_OPTIONS = {
|
|
2909
|
-
color: [0, 0, 0, 0], // Transparent background color
|
|
2910
|
-
depth: 1,
|
|
2911
|
-
};
|
|
2912
|
-
|
|
2913
|
-
const MOUSE_MODE_PANZOOM = 'panZoom';
|
|
2914
|
-
const MOUSE_MODE_LASSO = 'lasso';
|
|
2915
|
-
const MOUSE_MODE_ROTATE = 'rotate';
|
|
2916
|
-
const MOUSE_MODES = [
|
|
2917
|
-
MOUSE_MODE_PANZOOM,
|
|
2918
|
-
MOUSE_MODE_LASSO,
|
|
2919
|
-
MOUSE_MODE_ROTATE,
|
|
2920
|
-
];
|
|
2921
|
-
const DEFAULT_MOUSE_MODE = MOUSE_MODE_PANZOOM;
|
|
2922
|
-
|
|
2923
|
-
// Easing
|
|
2924
|
-
const EASING_FNS = {
|
|
2925
|
-
cubicIn,
|
|
2926
|
-
cubicInOut,
|
|
2927
|
-
cubicOut,
|
|
2928
|
-
linear,
|
|
2929
|
-
quadIn,
|
|
2930
|
-
quadInOut,
|
|
2931
|
-
quadOut,
|
|
2932
|
-
};
|
|
2933
|
-
const DEFAULT_EASING = cubicInOut;
|
|
2934
|
-
|
|
2935
|
-
const CONTINUOUS = 'continuous';
|
|
2936
|
-
const CATEGORICAL = 'categorical';
|
|
2937
|
-
const VALUE_ZW_DATA_TYPES = [CONTINUOUS, CATEGORICAL];
|
|
2938
|
-
|
|
2939
|
-
// Default lasso
|
|
2940
|
-
const LASSO_CLEAR_ON_DESELECT = 'deselect';
|
|
2941
|
-
const LASSO_CLEAR_ON_END = 'lassoEnd';
|
|
2942
|
-
const LASSO_CLEAR_EVENTS = [LASSO_CLEAR_ON_DESELECT, LASSO_CLEAR_ON_END];
|
|
2943
|
-
const LASSO_BRUSH_MIN_MIN_DIST = 3;
|
|
2944
|
-
const DEFAULT_LASSO_COLOR = [0, 0.666666667, 1, 1];
|
|
2945
|
-
const DEFAULT_LASSO_LINE_WIDTH = 2;
|
|
2946
|
-
const DEFAULT_LASSO_INITIATOR = false;
|
|
2947
|
-
const DEFAULT_LASSO_MIN_DELAY = 10;
|
|
2948
|
-
const DEFAULT_LASSO_MIN_DIST = 3;
|
|
2949
|
-
const DEFAULT_LASSO_CLEAR_EVENT = LASSO_CLEAR_ON_END;
|
|
2950
|
-
const DEFAULT_LASSO_ON_LONG_PRESS = false;
|
|
2951
|
-
const DEFAULT_LASSO_LONG_PRESS_TIME = 750;
|
|
2952
|
-
const DEFAULT_LASSO_LONG_PRESS_AFTER_EFFECT_TIME = 500;
|
|
2953
|
-
const DEFAULT_LASSO_LONG_PRESS_EFFECT_DELAY = 100;
|
|
2954
|
-
const DEFAULT_LASSO_LONG_PRESS_REVERT_EFFECT_TIME = 250;
|
|
2955
|
-
const DEFAULT_LASSO_BRUSH_SIZE = 24;
|
|
2956
|
-
const DEFAULT_LASSO_MODE = null;
|
|
2957
|
-
|
|
2958
|
-
// Key mapping
|
|
2959
|
-
const KEY_ACTION_INTERSECT = 'intersect';
|
|
2960
|
-
const KEY_ACTION_LASSO = 'lasso';
|
|
2961
|
-
const KEY_ACTION_ROTATE = 'rotate';
|
|
2962
|
-
const KEY_ACTION_MERGE = 'merge';
|
|
2963
|
-
const KEY_ACTION_REMOVE = 'remove';
|
|
2964
|
-
const KEY_ACTIONS = [
|
|
2965
|
-
KEY_ACTION_LASSO,
|
|
2966
|
-
KEY_ACTION_ROTATE,
|
|
2967
|
-
KEY_ACTION_INTERSECT,
|
|
2968
|
-
KEY_ACTION_MERGE,
|
|
2969
|
-
KEY_ACTION_REMOVE,
|
|
2970
|
-
];
|
|
2971
|
-
const KEY_ALT = 'alt';
|
|
2972
|
-
const KEY_CMD = 'cmd';
|
|
2973
|
-
const KEY_CTRL = 'ctrl';
|
|
2974
|
-
const KEY_META = 'meta';
|
|
2975
|
-
const KEY_SHIFT = 'shift';
|
|
2976
|
-
const KEYS = [KEY_ALT, KEY_CMD, KEY_CTRL, KEY_META, KEY_SHIFT];
|
|
2977
|
-
const DEFAULT_ACTION_KEY_MAP = {
|
|
2978
|
-
[KEY_ACTION_REMOVE]: KEY_ALT,
|
|
2979
|
-
[KEY_ACTION_ROTATE]: KEY_ALT,
|
|
2980
|
-
[KEY_ACTION_LASSO]: KEY_SHIFT,
|
|
2981
|
-
[KEY_ACTION_MERGE]: KEY_CMD,
|
|
2982
|
-
};
|
|
2983
|
-
|
|
2984
|
-
// Default attribute
|
|
2985
|
-
const DEFAULT_DATA_ASPECT_RATIO = 1;
|
|
2986
|
-
const DEFAULT_WIDTH = AUTO;
|
|
2987
|
-
const DEFAULT_HEIGHT = AUTO;
|
|
2988
|
-
const DEFAULT_GAMMA = 1;
|
|
2989
|
-
|
|
2990
|
-
// Default styles
|
|
2991
|
-
const MIN_POINT_SIZE = 1;
|
|
2992
|
-
const DEFAULT_POINT_SCALE_MODE = 'asinh';
|
|
2993
|
-
const DEFAULT_POINT_SIZE = 6;
|
|
2994
|
-
const DEFAULT_POINT_SIZE_SELECTED = 2;
|
|
2995
|
-
const DEFAULT_POINT_OUTLINE_WIDTH = 2;
|
|
2996
|
-
const DEFAULT_SIZE_BY = null;
|
|
2997
|
-
const DEFAULT_POINT_CONNECTION_SIZE = 2;
|
|
2998
|
-
const DEFAULT_POINT_CONNECTION_SIZE_ACTIVE = 2;
|
|
2999
|
-
const DEFAULT_POINT_CONNECTION_SIZE_BY = null;
|
|
3000
|
-
const DEFAULT_POINT_CONNECTION_OPACITY = null;
|
|
3001
|
-
const DEFAULT_POINT_CONNECTION_OPACITY_BY = null;
|
|
3002
|
-
const DEFAULT_POINT_CONNECTION_OPACITY_ACTIVE = 0.66;
|
|
3003
|
-
const DEFAULT_OPACITY = 1;
|
|
3004
|
-
const DEFAULT_OPACITY_BY = null;
|
|
3005
|
-
const DEFAULT_OPACITY_BY_DENSITY_FILL = 0.15;
|
|
3006
|
-
const DEFAULT_OPACITY_BY_DENSITY_DEBOUNCE_TIME = 25;
|
|
3007
|
-
const DEFAULT_OPACITY_INACTIVE_MAX = 1;
|
|
3008
|
-
const DEFAULT_OPACITY_INACTIVE_SCALE = 1;
|
|
3009
|
-
const DEFAULT_COLOR_BY = null;
|
|
3010
|
-
const DEFAULT_COLOR_NORMAL = [0.66, 0.66, 0.66, DEFAULT_OPACITY];
|
|
3011
|
-
const DEFAULT_COLOR_ACTIVE = [0, 0.55, 1, 1];
|
|
3012
|
-
const DEFAULT_COLOR_HOVER = [1, 1, 1, 1];
|
|
3013
|
-
const DEFAULT_COLOR_BG = [0, 0, 0, 1];
|
|
3014
|
-
const DEFAULT_POINT_CONNECTION_COLOR_BY = null;
|
|
3015
|
-
const DEFAULT_POINT_CONNECTION_COLOR_NORMAL = [0.66, 0.66, 0.66, 0.2];
|
|
3016
|
-
const DEFAULT_POINT_CONNECTION_COLOR_ACTIVE = [0, 0.55, 1, 1];
|
|
3017
|
-
const DEFAULT_POINT_CONNECTION_COLOR_HOVER = [1, 1, 1, 1];
|
|
3018
|
-
|
|
3019
|
-
// Annotations
|
|
3020
|
-
const DEFAULT_ANNOTATION_LINE_COLOR = [1, 1, 1, 0.5];
|
|
3021
|
-
const DEFAULT_ANNOTATION_LINE_WIDTH = 1;
|
|
3022
|
-
const DEFAULT_ANNOTATION_HVLINE_LIMIT = 1000;
|
|
3023
|
-
|
|
3024
|
-
// Default view
|
|
3025
|
-
const DEFAULT_TARGET = [0, 0];
|
|
3026
|
-
const DEFAULT_DISTANCE = 1;
|
|
3027
|
-
const DEFAULT_ROTATION = 0;
|
|
3028
|
-
// biome-ignore format: the array should not be formatted
|
|
3029
|
-
const DEFAULT_VIEW = new Float32Array([
|
|
3030
|
-
1, 0, 0, 0,
|
|
3031
|
-
0, 1, 0, 0,
|
|
3032
|
-
0, 0, 1, 0,
|
|
3033
|
-
0, 0, 0, 1,
|
|
3034
|
-
]);
|
|
3035
|
-
|
|
3036
|
-
// Error codes
|
|
3037
|
-
const IMAGE_LOAD_ERROR = 'IMAGE_LOAD_ERROR';
|
|
3038
|
-
|
|
3039
|
-
// Default misc
|
|
3040
|
-
const DEFAULT_BACKGROUND_IMAGE = null;
|
|
3041
|
-
const DEFAULT_SHOW_RETICLE = false;
|
|
3042
|
-
const DEFAULT_RETICLE_COLOR = [1, 1, 1, 0.5];
|
|
3043
|
-
const DEFAULT_DESELECT_ON_DBL_CLICK = true;
|
|
3044
|
-
const DEFAULT_DESELECT_ON_ESCAPE = true;
|
|
3045
|
-
const DEFAULT_SHOW_POINT_CONNECTIONS = false;
|
|
3046
|
-
const DEFAULT_POINT_CONNECTION_MAX_INT_POINTS_PER_SEGMENT = 100;
|
|
3047
|
-
const DEFAULT_POINT_CONNECTION_INT_POINTS_TOLERANCE = 1 / 500;
|
|
3048
|
-
const DEFAULT_POINT_SIZE_MOUSE_DETECTION = 'auto';
|
|
3049
|
-
const DEFAULT_PERFORMANCE_MODE = false;
|
|
3050
|
-
const SINGLE_CLICK_DELAY = 200;
|
|
3051
|
-
const LONG_CLICK_TIME = 500;
|
|
3052
|
-
const Z_NAMES = new Set(['z', 'valueZ', 'valueA', 'value1', 'category']);
|
|
3053
|
-
const W_NAMES = new Set(['w', 'valueW', 'valueB', 'value2', 'value']);
|
|
3054
|
-
const DEFAULT_IMAGE_LOAD_TIMEOUT = 15000;
|
|
3055
|
-
const DEFAULT_SPATIAL_INDEX_USE_WORKER = undefined;
|
|
3056
|
-
const DEFAULT_CAMERA_IS_FIXED = false;
|
|
3057
|
-
const DEFAULT_ANTI_ALIASING = 0.5;
|
|
3058
|
-
const DEFAULT_PIXEL_ALIGNED = false;
|
|
3059
|
-
const DEFAULT_LASSO_TYPE = 'lasso';
|
|
3060
|
-
const SKIP_DEPRECATION_VALUE_TRANSLATION = Symbol(
|
|
3061
|
-
'SKIP_DEPRECATION_VALUE_TRANSLATION',
|
|
3062
|
-
);
|
|
3063
|
-
|
|
3064
|
-
// Error messages
|
|
3065
|
-
const ERROR_POINTS_NOT_DRAWN = 'Points have not been drawn';
|
|
3066
|
-
const ERROR_INSTANCE_IS_DESTROYED = 'The instance was already destroyed';
|
|
3067
|
-
const ERROR_IS_DRAWING =
|
|
3068
|
-
'Ignoring draw call as the previous draw call has not yet finished. To avoid this warning `await` the draw call.';
|
|
3069
|
-
|
|
3070
3049
|
const getInTime = (p, time, extraTime) => (1 - p) * time + extraTime;
|
|
3071
3050
|
|
|
3072
3051
|
const getMainInAnimation = (t, d) =>
|
|
@@ -3497,10 +3476,10 @@ const createLasso = (
|
|
|
3497
3476
|
initiatorParentElement: initialInitiatorParentElement = document.body,
|
|
3498
3477
|
longPressIndicatorParentElement:
|
|
3499
3478
|
initialLongPressIndicatorParentElement = document.body,
|
|
3500
|
-
minDelay: initialMinDelay = DEFAULT_LASSO_MIN_DELAY
|
|
3501
|
-
minDist: initialMinDist = DEFAULT_LASSO_MIN_DIST
|
|
3479
|
+
minDelay: initialMinDelay = DEFAULT_LASSO_MIN_DELAY,
|
|
3480
|
+
minDist: initialMinDist = DEFAULT_LASSO_MIN_DIST,
|
|
3502
3481
|
pointNorm: initialPointNorm = identity,
|
|
3503
|
-
type: initialType = DEFAULT_LASSO_TYPE
|
|
3482
|
+
type: initialType = DEFAULT_LASSO_TYPE,
|
|
3504
3483
|
brushSize: initialBrushSize = DEFAULT_BRUSH_SIZE,
|
|
3505
3484
|
} = {},
|
|
3506
3485
|
) => {
|
|
@@ -4150,1052 +4129,1041 @@ const createLasso = (
|
|
|
4150
4129
|
});
|
|
4151
4130
|
|
|
4152
4131
|
initiatorParentElement.appendChild(initiator);
|
|
4153
|
-
longPressIndicatorParentElement.appendChild(longPress);
|
|
4154
|
-
|
|
4155
|
-
set({
|
|
4156
|
-
onDraw,
|
|
4157
|
-
onStart,
|
|
4158
|
-
onEnd,
|
|
4159
|
-
enableInitiator,
|
|
4160
|
-
initiatorParentElement,
|
|
4161
|
-
type,
|
|
4162
|
-
brushSize,
|
|
4163
|
-
});
|
|
4164
|
-
|
|
4165
|
-
return pipe(
|
|
4166
|
-
withStaticProperty('initiator', initiator),
|
|
4167
|
-
withStaticProperty('longPressIndicator', longPress),
|
|
4168
|
-
withPublicMethods(),
|
|
4169
|
-
withConstructor(createLasso),
|
|
4170
|
-
)({});
|
|
4171
|
-
};
|
|
4172
|
-
|
|
4173
|
-
/**
|
|
4174
|
-
* Check if all GL extensions are supported and enabled and warn otherwise
|
|
4175
|
-
* @param {import('regl').Regl} regl Regl instance to be tested
|
|
4176
|
-
* @param {boolean} silent If `true` the function will not print `console.warn` statements
|
|
4177
|
-
* @return {boolean} If `true` all required GL extensions are supported
|
|
4178
|
-
*/
|
|
4179
|
-
const checkReglExtensions = (regl, silent) => {
|
|
4180
|
-
if (!regl) {
|
|
4181
|
-
return false;
|
|
4182
|
-
}
|
|
4183
|
-
return GL_EXTENSIONS.reduce((every, extension) => {
|
|
4184
|
-
if (!regl.hasExtension(extension)) {
|
|
4185
|
-
if (!silent) {
|
|
4186
|
-
// biome-ignore lint/suspicious/noConsole: This is a legitimately useful warning
|
|
4187
|
-
console.warn(
|
|
4188
|
-
`WebGL: ${extension} extension not supported. Scatterplot might not render properly`,
|
|
4189
|
-
);
|
|
4190
|
-
}
|
|
4191
|
-
return false;
|
|
4192
|
-
}
|
|
4193
|
-
return every;
|
|
4194
|
-
}, true);
|
|
4195
|
-
};
|
|
4132
|
+
longPressIndicatorParentElement.appendChild(longPress);
|
|
4196
4133
|
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
preserveDrawingBuffer: true,
|
|
4134
|
+
set({
|
|
4135
|
+
onDraw,
|
|
4136
|
+
onStart,
|
|
4137
|
+
onEnd,
|
|
4138
|
+
enableInitiator,
|
|
4139
|
+
initiatorParentElement,
|
|
4140
|
+
type,
|
|
4141
|
+
brushSize,
|
|
4206
4142
|
});
|
|
4207
|
-
const extensions = [];
|
|
4208
|
-
|
|
4209
|
-
// Needed to run the tests properly as the headless-gl doesn't support all
|
|
4210
|
-
// extensions, which is fine for the functional tests.
|
|
4211
|
-
for (const extension of GL_EXTENSIONS) {
|
|
4212
|
-
if (gl.getExtension(extension)) {
|
|
4213
|
-
extensions.push(extension);
|
|
4214
|
-
} else {
|
|
4215
|
-
// biome-ignore lint/suspicious/noConsole: This is a legitimately useful warning
|
|
4216
|
-
console.warn(
|
|
4217
|
-
`WebGL: ${extension} extension not supported. Scatterplot might not render properly`,
|
|
4218
|
-
);
|
|
4219
|
-
}
|
|
4220
|
-
}
|
|
4221
4143
|
|
|
4222
|
-
return
|
|
4144
|
+
return pipe(
|
|
4145
|
+
withStaticProperty('initiator', initiator),
|
|
4146
|
+
withStaticProperty('longPressIndicator', longPress),
|
|
4147
|
+
withPublicMethods(),
|
|
4148
|
+
withConstructor(createLasso),
|
|
4149
|
+
)({});
|
|
4223
4150
|
};
|
|
4224
4151
|
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
* @param {number} x1 X coordinate of the first point
|
|
4228
|
-
* @param {number} y1 Y coordinate of the first point
|
|
4229
|
-
* @param {number} x2 X coordinate of the second point
|
|
4230
|
-
* @param {number} y2 Y coordinate of the first point
|
|
4231
|
-
* @return {number} L2 distance
|
|
4232
|
-
*/
|
|
4233
|
-
const dist = (x1, y1, x2, y2) =>
|
|
4234
|
-
Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
|
|
4152
|
+
const FRAGMENT_SHADER$1 = `
|
|
4153
|
+
precision highp float;
|
|
4235
4154
|
|
|
4236
|
-
|
|
4237
|
-
* Get the bounding box of a set of 2D positions
|
|
4238
|
-
* @param {array} positions2d 2D positions to be checked
|
|
4239
|
-
* @return {array} Quadruple of form `[xMin, yMin, xMax, yMax]` defining the
|
|
4240
|
-
* bounding box
|
|
4241
|
-
*/
|
|
4242
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
4243
|
-
const getBBox = (positions2d) => {
|
|
4244
|
-
let xMin = Number.POSITIVE_INFINITY;
|
|
4245
|
-
let xMax = Number.NEGATIVE_INFINITY;
|
|
4246
|
-
let yMin = Number.POSITIVE_INFINITY;
|
|
4247
|
-
let yMax = Number.NEGATIVE_INFINITY;
|
|
4155
|
+
uniform float antiAliasing;
|
|
4248
4156
|
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
xMax = positions2d[i] > xMax ? positions2d[i] : xMax;
|
|
4252
|
-
yMin = positions2d[i + 1] < yMin ? positions2d[i + 1] : yMin;
|
|
4253
|
-
yMax = positions2d[i + 1] > yMax ? positions2d[i + 1] : yMax;
|
|
4254
|
-
}
|
|
4157
|
+
varying vec4 color;
|
|
4158
|
+
varying float finalPointSize;
|
|
4255
4159
|
|
|
4256
|
-
|
|
4257
|
-
|
|
4160
|
+
float linearstep(float edge0, float edge1, float x) {
|
|
4161
|
+
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
4162
|
+
}
|
|
4258
4163
|
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
*/
|
|
4264
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
4265
|
-
const isValidBBox = ([xMin, yMin, xMax, yMax]) =>
|
|
4266
|
-
Number.isFinite(xMin) &&
|
|
4267
|
-
Number.isFinite(yMin) &&
|
|
4268
|
-
Number.isFinite(xMax) &&
|
|
4269
|
-
Number.isFinite(yMax) &&
|
|
4270
|
-
xMax - xMin > 0 &&
|
|
4271
|
-
yMax - yMin > 0;
|
|
4164
|
+
void main() {
|
|
4165
|
+
vec2 c = gl_PointCoord * 2.0 - 1.0;
|
|
4166
|
+
float sdf = length(c) * finalPointSize;
|
|
4167
|
+
float alpha = linearstep(finalPointSize + antiAliasing, finalPointSize - antiAliasing, sdf);
|
|
4272
4168
|
|
|
4273
|
-
|
|
4169
|
+
gl_FragColor = vec4(color.rgb, alpha * color.a);
|
|
4170
|
+
}
|
|
4171
|
+
`;
|
|
4274
4172
|
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
* @param {string} hex HEX-encoded color string.
|
|
4278
|
-
* @param {boolean} isNormalize If `true` the returned RGB values will be
|
|
4279
|
-
* normalized to `[0,1]`.
|
|
4280
|
-
* @return {array} Triple holding the RGB values.
|
|
4281
|
-
*/
|
|
4282
|
-
const hexToRgb = (hex, isNormalize = false) =>
|
|
4283
|
-
hex
|
|
4284
|
-
.replace(REGEX_HEX_TO_RGB, (_m, r, g, b) => `#${r}${r}${g}${g}${b}${b}`)
|
|
4285
|
-
.substring(1)
|
|
4286
|
-
.match(/.{2}/g)
|
|
4287
|
-
.map((x) => Number.parseInt(x, 16) / 255 ** isNormalize);
|
|
4173
|
+
const createVertexShader = (globalState) => `
|
|
4174
|
+
precision highp float;
|
|
4288
4175
|
|
|
4289
|
-
|
|
4290
|
-
|
|
4176
|
+
uniform sampler2D colorTex;
|
|
4177
|
+
uniform float colorTexRes;
|
|
4178
|
+
uniform float colorTexEps;
|
|
4179
|
+
uniform sampler2D stateTex;
|
|
4180
|
+
uniform float stateTexRes;
|
|
4181
|
+
uniform float stateTexEps;
|
|
4182
|
+
uniform float devicePixelRatio;
|
|
4183
|
+
uniform sampler2D encodingTex;
|
|
4184
|
+
uniform float encodingTexRes;
|
|
4185
|
+
uniform float encodingTexEps;
|
|
4186
|
+
uniform float pointSizeExtra;
|
|
4187
|
+
uniform float pointOpacityMax;
|
|
4188
|
+
uniform float pointOpacityScale;
|
|
4189
|
+
uniform float numPoints;
|
|
4190
|
+
uniform float globalState;
|
|
4191
|
+
uniform float isColoredByZ;
|
|
4192
|
+
uniform float isColoredByW;
|
|
4193
|
+
uniform float isOpacityByZ;
|
|
4194
|
+
uniform float isOpacityByW;
|
|
4195
|
+
uniform float isOpacityByDensity;
|
|
4196
|
+
uniform float isSizedByZ;
|
|
4197
|
+
uniform float isSizedByW;
|
|
4198
|
+
uniform float isPixelAligned;
|
|
4199
|
+
uniform float colorMultiplicator;
|
|
4200
|
+
uniform float opacityMultiplicator;
|
|
4201
|
+
uniform float opacityDensity;
|
|
4202
|
+
uniform float sizeMultiplicator;
|
|
4203
|
+
uniform float numColorStates;
|
|
4204
|
+
uniform float pointScale;
|
|
4205
|
+
uniform float drawingBufferWidth;
|
|
4206
|
+
uniform float drawingBufferHeight;
|
|
4207
|
+
uniform mat4 modelViewProjection;
|
|
4291
4208
|
|
|
4292
|
-
|
|
4209
|
+
attribute vec2 stateIndex;
|
|
4293
4210
|
|
|
4294
|
-
|
|
4211
|
+
varying vec4 color;
|
|
4212
|
+
varying float finalPointSize;
|
|
4295
4213
|
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
* @param {array} choices Array of acceptable choices
|
|
4299
|
-
* @param {*} defaultOption Default choice
|
|
4300
|
-
* @return {function} Function limiting the choices
|
|
4301
|
-
*/
|
|
4302
|
-
const limit = (choices, defaultChoice) => (choice) =>
|
|
4303
|
-
choices.indexOf(choice) >= 0 ? choice : defaultChoice;
|
|
4214
|
+
void main() {
|
|
4215
|
+
vec4 state = texture2D(stateTex, stateIndex);
|
|
4304
4216
|
|
|
4305
|
-
|
|
4306
|
-
*
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
) =>
|
|
4316
|
-
new Promise((resolve, reject) => {
|
|
4317
|
-
const image = new Image();
|
|
4318
|
-
if (isCrossOrigin) {
|
|
4319
|
-
image.crossOrigin = 'anonymous';
|
|
4320
|
-
}
|
|
4321
|
-
image.src = src;
|
|
4322
|
-
image.onload = () => {
|
|
4323
|
-
resolve(image);
|
|
4324
|
-
};
|
|
4325
|
-
const rejectPromise = () => {
|
|
4326
|
-
reject(new Error(IMAGE_LOAD_ERROR));
|
|
4327
|
-
};
|
|
4328
|
-
image.onerror = rejectPromise;
|
|
4329
|
-
setTimeout(rejectPromise, timeout);
|
|
4330
|
-
});
|
|
4217
|
+
if (isPixelAligned < 0.5) {
|
|
4218
|
+
gl_Position = modelViewProjection * vec4(state.x, state.y, 0.0, 1.0);
|
|
4219
|
+
} else {
|
|
4220
|
+
vec4 clipSpacePosition = modelViewProjection * vec4(state.x, state.y, 0.0, 1.0);
|
|
4221
|
+
vec2 ndcPosition = clipSpacePosition.xy / clipSpacePosition.w;
|
|
4222
|
+
vec2 pixelPos = 0.5 * (ndcPosition + 1.0) * vec2(drawingBufferWidth, drawingBufferHeight);
|
|
4223
|
+
pixelPos = floor(pixelPos + 0.5); // Snap to nearest pixel
|
|
4224
|
+
vec2 snappedPosition = (pixelPos / vec2(drawingBufferWidth, drawingBufferHeight)) * 2.0 - 1.0;
|
|
4225
|
+
gl_Position = vec4(snappedPosition, 0.0, 1.0);
|
|
4226
|
+
}
|
|
4331
4227
|
|
|
4332
|
-
/**
|
|
4333
|
-
* @deprecated Please use `scatterplot.createTextureFromUrl(url)`
|
|
4334
|
-
*
|
|
4335
|
-
* Create a Regl texture from an URL.
|
|
4336
|
-
* @param {import('regl').Regl} regl Regl instance used for creating the texture.
|
|
4337
|
-
* @param {string} url Source URL of the image.
|
|
4338
|
-
* @return {Promise<import('regl').Texture2D>} Promise resolving to the texture object.
|
|
4339
|
-
*/
|
|
4340
|
-
const createTextureFromUrl = (
|
|
4341
|
-
regl,
|
|
4342
|
-
url,
|
|
4343
|
-
timeout = DEFAULT_IMAGE_LOAD_TIMEOUT,
|
|
4344
|
-
) =>
|
|
4345
|
-
new Promise((resolve, reject) => {
|
|
4346
|
-
loadImage(
|
|
4347
|
-
url,
|
|
4348
|
-
url.indexOf(window.location.origin) !== 0 && url.indexOf('base64') === -1,
|
|
4349
|
-
timeout,
|
|
4350
|
-
)
|
|
4351
|
-
.then((image) => {
|
|
4352
|
-
resolve(regl.texture(image));
|
|
4353
|
-
})
|
|
4354
|
-
.catch((error) => {
|
|
4355
|
-
reject(error);
|
|
4356
|
-
});
|
|
4357
|
-
});
|
|
4358
4228
|
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
* @param {boolean} isNormalize If `true` the returned RGBA values will be
|
|
4363
|
-
* normalized to `[0,1]`.
|
|
4364
|
-
* @return {array} Triple holding the RGBA values.
|
|
4365
|
-
*/
|
|
4366
|
-
const hexToRgba = (hex, isNormalize = false) => [
|
|
4367
|
-
...hexToRgb(hex, isNormalize),
|
|
4368
|
-
255 ** !isNormalize,
|
|
4369
|
-
];
|
|
4229
|
+
// Determine color index
|
|
4230
|
+
float colorIndexZ = isColoredByZ * floor(state.z * colorMultiplicator);
|
|
4231
|
+
float colorIndexW = isColoredByW * floor(state.w * colorMultiplicator);
|
|
4370
4232
|
|
|
4371
|
-
|
|
4233
|
+
// Multiply by the number of color states per color
|
|
4234
|
+
// I.e., normal, active, hover, background, etc.
|
|
4235
|
+
float colorIndex = (colorIndexZ + colorIndexW) * numColorStates;
|
|
4372
4236
|
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4237
|
+
// Half a "pixel" or "texel" in texture coordinates
|
|
4238
|
+
float colorLinearIndex = colorIndex + globalState;
|
|
4239
|
+
|
|
4240
|
+
// Need to add cEps here to avoid floating point issue that can lead to
|
|
4241
|
+
// dramatic changes in which color is loaded as floor(3/2.9999) = 1 but
|
|
4242
|
+
// floor(3/3.0001) = 0!
|
|
4243
|
+
float colorRowIndex = floor((colorLinearIndex + colorTexEps) / colorTexRes);
|
|
4244
|
+
|
|
4245
|
+
vec2 colorTexIndex = vec2(
|
|
4246
|
+
(colorLinearIndex / colorTexRes) - colorRowIndex + colorTexEps,
|
|
4247
|
+
colorRowIndex / colorTexRes + colorTexEps
|
|
4248
|
+
);
|
|
4379
4249
|
|
|
4380
|
-
|
|
4381
|
-
* Tests if a number is in `[0,1]`.
|
|
4382
|
-
* @param {number} x Number to be tested.
|
|
4383
|
-
* @return {boolean} If `true` the number is in `[0,1]`.
|
|
4384
|
-
*/
|
|
4385
|
-
const isNormFloat = (x) => x >= 0 && x <= 1;
|
|
4250
|
+
color = texture2D(colorTex, colorTexIndex);
|
|
4386
4251
|
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
*
|
|
4390
|
-
|
|
4391
|
-
*/
|
|
4392
|
-
const isNormFloatArray = (a) => Array.isArray(a) && a.every(isNormFloat);
|
|
4252
|
+
// Retrieve point size
|
|
4253
|
+
float pointSizeIndexZ = isSizedByZ * floor(state.z * sizeMultiplicator);
|
|
4254
|
+
float pointSizeIndexW = isSizedByW * floor(state.w * sizeMultiplicator);
|
|
4255
|
+
float pointSizeIndex = pointSizeIndexZ + pointSizeIndexW;
|
|
4393
4256
|
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
* @param {number} px X-coordinate of test point
|
|
4401
|
-
* @param {number} py Y-coordinate of test point
|
|
4402
|
-
* @return {number} Positive if counterclockwise, negative if clockwise
|
|
4403
|
-
*/
|
|
4404
|
-
function crossProduct(x1, y1, x2, y2, px, py) {
|
|
4405
|
-
return (x2 - x1) * (py - y1) - (px - x1) * (y2 - y1);
|
|
4406
|
-
}
|
|
4257
|
+
float pointSizeRowIndex = floor((pointSizeIndex + encodingTexEps) / encodingTexRes);
|
|
4258
|
+
vec2 pointSizeTexIndex = vec2(
|
|
4259
|
+
(pointSizeIndex / encodingTexRes) - pointSizeRowIndex + encodingTexEps,
|
|
4260
|
+
pointSizeRowIndex / encodingTexRes + encodingTexEps
|
|
4261
|
+
);
|
|
4262
|
+
float pointSize = texture2D(encodingTex, pointSizeTexIndex).x;
|
|
4407
4263
|
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
* @return {boolean} True if point lies within the polygon
|
|
4414
|
-
*/
|
|
4415
|
-
const isPointInPolygon = (polygon, [px, py] = []) => {
|
|
4416
|
-
let winding = 0;
|
|
4264
|
+
// Retrieve opacity
|
|
4265
|
+
${
|
|
4266
|
+
(() => {
|
|
4267
|
+
// Drawing the inner border of selected points
|
|
4268
|
+
if (globalState === 3) return '';
|
|
4417
4269
|
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4270
|
+
// Draw points with opacity encoding or dynamic opacity
|
|
4271
|
+
return `
|
|
4272
|
+
if (isOpacityByDensity < 0.5) {
|
|
4273
|
+
float opacityIndexZ = isOpacityByZ * floor(state.z * opacityMultiplicator);
|
|
4274
|
+
float opacityIndexW = isOpacityByW * floor(state.w * opacityMultiplicator);
|
|
4275
|
+
float opacityIndex = opacityIndexZ + opacityIndexW;
|
|
4423
4276
|
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4277
|
+
float opacityRowIndex = floor((opacityIndex + encodingTexEps) / encodingTexRes);
|
|
4278
|
+
vec2 opacityTexIndex = vec2(
|
|
4279
|
+
(opacityIndex / encodingTexRes) - opacityRowIndex + encodingTexEps,
|
|
4280
|
+
opacityRowIndex / encodingTexRes + encodingTexEps
|
|
4281
|
+
);
|
|
4282
|
+
color.a = texture2D(encodingTex, opacityTexIndex)[${1 + globalState}];
|
|
4283
|
+
} else {
|
|
4284
|
+
color.a = min(1.0, opacityDensity + globalState);
|
|
4429
4285
|
}
|
|
4430
|
-
|
|
4431
|
-
}
|
|
4432
|
-
const orientation = crossProduct(x1, y1, x2, y2, px, py);
|
|
4433
|
-
if (orientation < 0) {
|
|
4434
|
-
winding--;
|
|
4435
|
-
}
|
|
4436
|
-
}
|
|
4437
|
-
|
|
4438
|
-
j = i;
|
|
4286
|
+
`;
|
|
4287
|
+
})()
|
|
4439
4288
|
}
|
|
4440
4289
|
|
|
4441
|
-
|
|
4442
|
-
|
|
4290
|
+
color.a = min(pointOpacityMax, color.a) * pointOpacityScale;
|
|
4291
|
+
finalPointSize = (pointSize * pointScale) + pointSizeExtra;
|
|
4292
|
+
gl_PointSize = finalPointSize;
|
|
4293
|
+
}
|
|
4294
|
+
`;
|
|
4443
4295
|
|
|
4444
|
-
|
|
4445
|
-
* Tests if a variable is a string
|
|
4446
|
-
* @param {*} s Variable to be tested
|
|
4447
|
-
* @return {boolean} If `true` variable is a string
|
|
4448
|
-
*/
|
|
4449
|
-
const isString = (s) => typeof s === 'string' || s instanceof String;
|
|
4296
|
+
const FRAGMENT_SHADER = `precision highp float;
|
|
4450
4297
|
|
|
4451
|
-
|
|
4452
|
-
* Tests if a number is an interger and in `[0,255]`.
|
|
4453
|
-
* @param {number} x Number to be tested.
|
|
4454
|
-
* @return {boolean} If `true` the number is an interger and in `[0,255]`.
|
|
4455
|
-
*/
|
|
4456
|
-
const isUint8 = (x) => Number.isInteger(x) && x >= 0 && x <= 255;
|
|
4298
|
+
varying vec4 color;
|
|
4457
4299
|
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
*/
|
|
4463
|
-
const isUint8Array = (a) => Array.isArray(a) && a.every(isUint8);
|
|
4300
|
+
void main() {
|
|
4301
|
+
gl_FragColor = color;
|
|
4302
|
+
}
|
|
4303
|
+
`;
|
|
4464
4304
|
|
|
4465
|
-
|
|
4466
|
-
* Tests if an array is encoding an RGB color.
|
|
4467
|
-
* @param {array} rgb Array to be tested
|
|
4468
|
-
* @return {boolean} If `true` the array hold a triple of Uint8 numbers or
|
|
4469
|
-
* a triple of normalized floats.
|
|
4470
|
-
*/
|
|
4471
|
-
const isRgb = (rgb) =>
|
|
4472
|
-
rgb.length === 3 && (isNormFloatArray(rgb) || isUint8Array(rgb));
|
|
4305
|
+
const SHADER$1 = `precision highp float;
|
|
4473
4306
|
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
* @return {boolean} If `true` the array hold a quadruple of Uint8 numbers or
|
|
4478
|
-
* a quadruple of normalized floats.
|
|
4479
|
-
*/
|
|
4480
|
-
const isRgba = (rgba) =>
|
|
4481
|
-
rgba.length === 4 && (isNormFloatArray(rgba) || isUint8Array(rgba));
|
|
4307
|
+
uniform sampler2D startStateTex;
|
|
4308
|
+
uniform sampler2D endStateTex;
|
|
4309
|
+
uniform float t;
|
|
4482
4310
|
|
|
4483
|
-
|
|
4484
|
-
* Test if a color is multiple colors
|
|
4485
|
-
* @param {*} color To be tested
|
|
4486
|
-
* @return {boolean} If `true`, `color` is an array of colors.
|
|
4487
|
-
*/
|
|
4488
|
-
const isMultipleColors = (color) =>
|
|
4489
|
-
Array.isArray(color) &&
|
|
4490
|
-
color.length > 0 &&
|
|
4491
|
-
(Array.isArray(color[0]) || isString(color[0]));
|
|
4311
|
+
varying vec2 particleTextureIndex;
|
|
4492
4312
|
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
return false;
|
|
4499
|
-
}
|
|
4313
|
+
void main() {
|
|
4314
|
+
// Interpolate x, y, and value
|
|
4315
|
+
vec3 start = texture2D(startStateTex, particleTextureIndex).xyw;
|
|
4316
|
+
vec3 end = texture2D(endStateTex, particleTextureIndex).xyw;
|
|
4317
|
+
vec3 curr = start * (1.0 - t) + end * t;
|
|
4500
4318
|
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
}
|
|
4319
|
+
// The category cannot be interpolated
|
|
4320
|
+
float endCategory = texture2D(endStateTex, particleTextureIndex).z;
|
|
4504
4321
|
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
return false;
|
|
4508
|
-
}
|
|
4322
|
+
gl_FragColor = vec4(curr.xy, endCategory, curr.z);
|
|
4323
|
+
}`;
|
|
4509
4324
|
|
|
4510
|
-
|
|
4511
|
-
const [r2, g2, b2, a2] = b[i];
|
|
4512
|
-
return r1 === r2 && g1 === g2 && b1 === b2 && a1 === a2;
|
|
4513
|
-
});
|
|
4514
|
-
};
|
|
4325
|
+
const SHADER = `precision highp float;
|
|
4515
4326
|
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
* https://jsperf.com/math-min-max-vs-ternary-vs-if/24 `Math.max` is not
|
|
4519
|
-
* very fast
|
|
4520
|
-
* @param {number} a Value A
|
|
4521
|
-
* @param {number} b Value B
|
|
4522
|
-
* @return {boolean} If `true` A is greater than B.
|
|
4523
|
-
*/
|
|
4524
|
-
const max = (a, b) => (a > b ? a : b);
|
|
4327
|
+
attribute vec2 position;
|
|
4328
|
+
varying vec2 particleTextureIndex;
|
|
4525
4329
|
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
* @return {boolean} If `true` A is smaller than B.
|
|
4533
|
-
*/
|
|
4534
|
-
const min = (a, b) => (a < b ? a : b);
|
|
4330
|
+
void main() {
|
|
4331
|
+
// map normalized device coords to texture coords
|
|
4332
|
+
particleTextureIndex = 0.5 * (1.0 + position);
|
|
4333
|
+
|
|
4334
|
+
gl_Position = vec4(position, 0, 1);
|
|
4335
|
+
}`;
|
|
4535
4336
|
|
|
4536
4337
|
/**
|
|
4537
|
-
*
|
|
4538
|
-
* @param {
|
|
4539
|
-
*
|
|
4540
|
-
* @
|
|
4541
|
-
* normalized to `[0,1]`.
|
|
4542
|
-
* @return {array} Quadruple defining an RGBA color.
|
|
4338
|
+
* Check if all GL extensions are supported and enabled and warn otherwise
|
|
4339
|
+
* @param {import('regl').Regl} regl Regl instance to be tested
|
|
4340
|
+
* @param {boolean} silent If `true` the function will not print `console.warn` statements
|
|
4341
|
+
* @return {boolean} If `true` all required GL extensions are supported
|
|
4543
4342
|
*/
|
|
4544
|
-
const
|
|
4545
|
-
if (
|
|
4546
|
-
|
|
4547
|
-
if (
|
|
4548
|
-
(shouldNormalize && isNormalized) ||
|
|
4549
|
-
!(shouldNormalize || isNormalized)
|
|
4550
|
-
) {
|
|
4551
|
-
return color;
|
|
4552
|
-
}
|
|
4553
|
-
if (shouldNormalize && !isNormalized) {
|
|
4554
|
-
return color.map((x) => x / 255);
|
|
4555
|
-
}
|
|
4556
|
-
return color.map((x) => x * 255);
|
|
4557
|
-
}
|
|
4558
|
-
|
|
4559
|
-
if (isRgb(color)) {
|
|
4560
|
-
const base = 255 ** !shouldNormalize;
|
|
4561
|
-
const isNormalized = isNormFloatArray(color);
|
|
4562
|
-
|
|
4563
|
-
if (
|
|
4564
|
-
(shouldNormalize && isNormalized) ||
|
|
4565
|
-
!(shouldNormalize || isNormalized)
|
|
4566
|
-
) {
|
|
4567
|
-
return [...color, base];
|
|
4568
|
-
}
|
|
4569
|
-
if (shouldNormalize && !isNormalized) {
|
|
4570
|
-
return [...color.map((x) => x / 255), base];
|
|
4571
|
-
}
|
|
4572
|
-
return [...color.map((x) => x * 255), base];
|
|
4573
|
-
}
|
|
4574
|
-
|
|
4575
|
-
if (isHex(color)) {
|
|
4576
|
-
return hexToRgba(color, shouldNormalize);
|
|
4343
|
+
const checkReglExtensions = (regl, silent) => {
|
|
4344
|
+
if (!regl) {
|
|
4345
|
+
return false;
|
|
4577
4346
|
}
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4347
|
+
return GL_EXTENSIONS.reduce((every, extension) => {
|
|
4348
|
+
if (!regl.hasExtension(extension)) {
|
|
4349
|
+
if (!silent) {
|
|
4350
|
+
// biome-ignore lint/suspicious/noConsole: This is a legitimately useful warning
|
|
4351
|
+
console.warn(
|
|
4352
|
+
`WebGL: ${extension} extension not supported. Scatterplot might not render properly`,
|
|
4353
|
+
);
|
|
4354
|
+
}
|
|
4355
|
+
return false;
|
|
4356
|
+
}
|
|
4357
|
+
return every;
|
|
4358
|
+
}, true);
|
|
4584
4359
|
};
|
|
4585
4360
|
|
|
4586
4361
|
/**
|
|
4587
|
-
*
|
|
4588
|
-
* @param
|
|
4589
|
-
* @return
|
|
4362
|
+
* Create a new Regl instance with `GL_EXTENSIONS` enables
|
|
4363
|
+
* @param {HTMLCanvasElement} canvas Canvas element to be rendered on
|
|
4364
|
+
* @return {import('regl').Regl} New Regl instance
|
|
4590
4365
|
*/
|
|
4591
|
-
const
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4366
|
+
const createRegl = (canvas) => {
|
|
4367
|
+
const gl = canvas.getContext('webgl', {
|
|
4368
|
+
antialias: true,
|
|
4369
|
+
preserveDrawingBuffer: true,
|
|
4370
|
+
});
|
|
4371
|
+
const extensions = [];
|
|
4372
|
+
|
|
4373
|
+
// Needed to run the tests properly as the headless-gl doesn't support all
|
|
4374
|
+
// extensions, which is fine for the functional tests.
|
|
4375
|
+
for (const extension of GL_EXTENSIONS) {
|
|
4376
|
+
if (gl.getExtension(extension)) {
|
|
4377
|
+
extensions.push(extension);
|
|
4595
4378
|
} else {
|
|
4596
|
-
|
|
4379
|
+
// biome-ignore lint/suspicious/noConsole: This is a legitimately useful warning
|
|
4380
|
+
console.warn(
|
|
4381
|
+
`WebGL: ${extension} extension not supported. Scatterplot might not render properly`,
|
|
4382
|
+
);
|
|
4597
4383
|
}
|
|
4598
|
-
|
|
4599
|
-
}, {});
|
|
4384
|
+
}
|
|
4600
4385
|
|
|
4601
|
-
|
|
4602
|
-
|
|
4386
|
+
return createOriginalRegl({ gl, extensions });
|
|
4387
|
+
};
|
|
4603
4388
|
|
|
4604
4389
|
/**
|
|
4605
|
-
*
|
|
4606
|
-
* @param
|
|
4607
|
-
* @param
|
|
4608
|
-
* @param
|
|
4609
|
-
* @
|
|
4390
|
+
* L2 distance between a pair of 2D points
|
|
4391
|
+
* @param {number} x1 X coordinate of the first point
|
|
4392
|
+
* @param {number} y1 Y coordinate of the first point
|
|
4393
|
+
* @param {number} x2 X coordinate of the second point
|
|
4394
|
+
* @param {number} y2 Y coordinate of the first point
|
|
4395
|
+
* @return {number} L2 distance
|
|
4610
4396
|
*/
|
|
4611
|
-
const
|
|
4612
|
-
Math.
|
|
4397
|
+
const dist = (x1, y1, x2, y2) =>
|
|
4398
|
+
Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
|
|
4613
4399
|
|
|
4614
4400
|
/**
|
|
4615
|
-
*
|
|
4616
|
-
* @param
|
|
4617
|
-
* @return
|
|
4401
|
+
* Get the bounding box of a set of 2D positions
|
|
4402
|
+
* @param {array} positions2d 2D positions to be checked
|
|
4403
|
+
* @return {array} Quadruple of form `[xMin, yMin, xMax, yMax]` defining the
|
|
4404
|
+
* bounding box
|
|
4618
4405
|
*/
|
|
4619
|
-
const
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
const length =
|
|
4625
|
-
Array.isArray(points.x) || ArrayBuffer.isView(points.x)
|
|
4626
|
-
? points.x.length
|
|
4627
|
-
: 0;
|
|
4406
|
+
const getBBox = (positions2d) => {
|
|
4407
|
+
let xMin = Number.POSITIVE_INFINITY;
|
|
4408
|
+
let xMax = Number.NEGATIVE_INFINITY;
|
|
4409
|
+
let yMin = Number.POSITIVE_INFINITY;
|
|
4410
|
+
let yMax = Number.NEGATIVE_INFINITY;
|
|
4628
4411
|
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
const getL =
|
|
4636
|
-
(Array.isArray(points.line) || ArrayBuffer.isView(points.line)) &&
|
|
4637
|
-
((i) => points.line[i]);
|
|
4412
|
+
for (let i = 0; i < positions2d.length; i += 2) {
|
|
4413
|
+
xMin = positions2d[i] < xMin ? positions2d[i] : xMin;
|
|
4414
|
+
xMax = positions2d[i] > xMax ? positions2d[i] : xMax;
|
|
4415
|
+
yMin = positions2d[i + 1] < yMin ? positions2d[i + 1] : yMin;
|
|
4416
|
+
yMax = positions2d[i + 1] > yMax ? positions2d[i + 1] : yMax;
|
|
4417
|
+
}
|
|
4638
4418
|
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
(Array.isArray(points.lineOrder) ||
|
|
4642
|
-
ArrayBuffer.isView(points.lineOrder)) &&
|
|
4643
|
-
((i) => points.lineOrder[i]);
|
|
4419
|
+
return [xMin, yMin, xMax, yMax];
|
|
4420
|
+
};
|
|
4644
4421
|
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
w &&
|
|
4658
|
-
(Array.isArray(points[w]) || ArrayBuffer.isView(points[w])) &&
|
|
4659
|
-
((i) => points[w][i])
|
|
4660
|
-
);
|
|
4661
|
-
})();
|
|
4422
|
+
/**
|
|
4423
|
+
* Test whether a bounding box is actually specifying an area
|
|
4424
|
+
* @param {array} bBox The bounding box to be checked
|
|
4425
|
+
* @return {array} `true` if the bounding box is valid
|
|
4426
|
+
*/
|
|
4427
|
+
const isValidBBox = ([xMin, yMin, xMax, yMax]) =>
|
|
4428
|
+
Number.isFinite(xMin) &&
|
|
4429
|
+
Number.isFinite(yMin) &&
|
|
4430
|
+
Number.isFinite(xMax) &&
|
|
4431
|
+
Number.isFinite(yMax) &&
|
|
4432
|
+
xMax - xMin > 0 &&
|
|
4433
|
+
yMax - yMin > 0;
|
|
4662
4434
|
|
|
4663
|
-
|
|
4664
|
-
resolve(
|
|
4665
|
-
points.x.map((x, i) => [
|
|
4666
|
-
x,
|
|
4667
|
-
getY(i),
|
|
4668
|
-
getZ(i),
|
|
4669
|
-
getW(i),
|
|
4670
|
-
getL(i),
|
|
4671
|
-
getLO(i),
|
|
4672
|
-
]),
|
|
4673
|
-
);
|
|
4674
|
-
} else if (getX && getY && getZ && getW && getL) {
|
|
4675
|
-
resolve(
|
|
4676
|
-
Array.from({ length }, (_, i) => [
|
|
4677
|
-
getX(i),
|
|
4678
|
-
getY(i),
|
|
4679
|
-
getZ(i),
|
|
4680
|
-
getW(i),
|
|
4681
|
-
getL(i),
|
|
4682
|
-
]),
|
|
4683
|
-
);
|
|
4684
|
-
} else if (getX && getY && getZ && getW) {
|
|
4685
|
-
resolve(
|
|
4686
|
-
Array.from({ length }, (_, i) => [
|
|
4687
|
-
getX(i),
|
|
4688
|
-
getY(i),
|
|
4689
|
-
getZ(i),
|
|
4690
|
-
getW(i),
|
|
4691
|
-
]),
|
|
4692
|
-
);
|
|
4693
|
-
} else if (getX && getY && getZ) {
|
|
4694
|
-
resolve(Array.from({ length }, (_, i) => [getX(i), getY(i), getZ(i)]));
|
|
4695
|
-
} else if (getX && getY) {
|
|
4696
|
-
resolve(Array.from({ length }, (_, i) => [getX(i), getY(i)]));
|
|
4697
|
-
} else {
|
|
4698
|
-
reject(new Error('You need to specify at least x and y'));
|
|
4699
|
-
}
|
|
4700
|
-
}
|
|
4701
|
-
});
|
|
4435
|
+
const REGEX_HEX_TO_RGB = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
4702
4436
|
|
|
4703
|
-
|
|
4704
|
-
|
|
4437
|
+
/**
|
|
4438
|
+
* Convert a HEX-encoded color to an RGB-encoded color
|
|
4439
|
+
* @param {string} hex HEX-encoded color string.
|
|
4440
|
+
* @param {boolean} isNormalize If `true` the returned RGB values will be
|
|
4441
|
+
* normalized to `[0,1]`.
|
|
4442
|
+
* @return {array} Triple holding the RGB values.
|
|
4443
|
+
*/
|
|
4444
|
+
const hexToRgb = (hex, isNormalize = false) =>
|
|
4445
|
+
hex
|
|
4446
|
+
.replace(REGEX_HEX_TO_RGB, (_m, r, g, b) => `#${r}${r}${g}${g}${b}${b}`)
|
|
4447
|
+
.substring(1)
|
|
4448
|
+
.match(/.{2}/g)
|
|
4449
|
+
.map((x) => Number.parseInt(x, 16) / 255 ** isNormalize);
|
|
4705
4450
|
|
|
4706
|
-
const
|
|
4707
|
-
|
|
4451
|
+
const isConditionalArray = (a, condition, { minLength = 0 } = {}) =>
|
|
4452
|
+
Array.isArray(a) && a.length >= minLength && a.every(condition);
|
|
4708
4453
|
|
|
4709
|
-
const
|
|
4710
|
-
Number.isFinite(annotation.x) &&
|
|
4711
|
-
Number.isFinite(annotation.y) &&
|
|
4712
|
-
Number.isFinite(annotation.width) &&
|
|
4713
|
-
Number.isFinite(annotation.height);
|
|
4454
|
+
const isPositiveNumber = (x) => !Number.isNaN(+x) && +x >= 0;
|
|
4714
4455
|
|
|
4715
|
-
const
|
|
4716
|
-
Number.isFinite(annotation.x1) &&
|
|
4717
|
-
Number.isFinite(annotation.y1) &&
|
|
4718
|
-
Number.isFinite(annotation.x2) &&
|
|
4719
|
-
Number.isFinite(annotation.x2);
|
|
4456
|
+
const isStrictlyPositiveNumber = (x) => !Number.isNaN(+x) && +x > 0;
|
|
4720
4457
|
|
|
4721
|
-
|
|
4722
|
-
|
|
4458
|
+
/**
|
|
4459
|
+
* Create a function to limit choices to a predefined list
|
|
4460
|
+
* @param {array} choices Array of acceptable choices
|
|
4461
|
+
* @param {*} defaultOption Default choice
|
|
4462
|
+
* @return {function} Function limiting the choices
|
|
4463
|
+
*/
|
|
4464
|
+
const limit = (choices, defaultChoice) => (choice) =>
|
|
4465
|
+
choices.indexOf(choice) >= 0 ? choice : defaultChoice;
|
|
4723
4466
|
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4467
|
+
/**
|
|
4468
|
+
* Promised-based image loading
|
|
4469
|
+
* @param {string} src Remote image source, i.e., a URL
|
|
4470
|
+
* @param {boolean} isCrossOrigin If `true` allow loading image from a source of another origin.
|
|
4471
|
+
* @return {Promise<HTMLImageElement>} Promise resolving to the image once its loaded
|
|
4472
|
+
*/
|
|
4473
|
+
const loadImage = (
|
|
4474
|
+
src,
|
|
4475
|
+
isCrossOrigin = false,
|
|
4476
|
+
timeout = DEFAULT_IMAGE_LOAD_TIMEOUT,
|
|
4477
|
+
) =>
|
|
4478
|
+
new Promise((resolve, reject) => {
|
|
4479
|
+
const image = new Image();
|
|
4480
|
+
if (isCrossOrigin) {
|
|
4481
|
+
image.crossOrigin = 'anonymous';
|
|
4734
4482
|
}
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
};
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
canvas = document.createElement('canvas'),
|
|
4746
|
-
gamma = DEFAULT_GAMMA,
|
|
4747
|
-
} = options;
|
|
4483
|
+
image.src = src;
|
|
4484
|
+
image.onload = () => {
|
|
4485
|
+
resolve(image);
|
|
4486
|
+
};
|
|
4487
|
+
const rejectPromise = () => {
|
|
4488
|
+
reject(new Error(IMAGE_LOAD_ERROR));
|
|
4489
|
+
};
|
|
4490
|
+
image.onerror = rejectPromise;
|
|
4491
|
+
setTimeout(rejectPromise, timeout);
|
|
4492
|
+
});
|
|
4748
4493
|
|
|
4749
|
-
|
|
4494
|
+
/**
|
|
4495
|
+
* @deprecated Please use `scatterplot.createTextureFromUrl(url)`
|
|
4496
|
+
*
|
|
4497
|
+
* Create a Regl texture from an URL.
|
|
4498
|
+
* @param {import('regl').Regl} regl Regl instance used for creating the texture.
|
|
4499
|
+
* @param {string} url Source URL of the image.
|
|
4500
|
+
* @return {Promise<import('regl').Texture2D>} Promise resolving to the texture object.
|
|
4501
|
+
*/
|
|
4502
|
+
const createTextureFromUrl = (
|
|
4503
|
+
regl,
|
|
4504
|
+
url,
|
|
4505
|
+
timeout = DEFAULT_IMAGE_LOAD_TIMEOUT,
|
|
4506
|
+
) =>
|
|
4507
|
+
new Promise((resolve, reject) => {
|
|
4508
|
+
loadImage(
|
|
4509
|
+
url,
|
|
4510
|
+
url.indexOf(window.location.origin) !== 0 && url.indexOf('base64') === -1,
|
|
4511
|
+
timeout,
|
|
4512
|
+
)
|
|
4513
|
+
.then((image) => {
|
|
4514
|
+
resolve(regl.texture(image));
|
|
4515
|
+
})
|
|
4516
|
+
.catch((error) => {
|
|
4517
|
+
reject(error);
|
|
4518
|
+
});
|
|
4519
|
+
});
|
|
4750
4520
|
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4521
|
+
/**
|
|
4522
|
+
* Convert a HEX-encoded color to an RGBA-encoded color
|
|
4523
|
+
* @param {string} hex HEX-encoded color string.
|
|
4524
|
+
* @param {boolean} isNormalize If `true` the returned RGBA values will be
|
|
4525
|
+
* normalized to `[0,1]`.
|
|
4526
|
+
* @return {array} Triple holding the RGBA values.
|
|
4527
|
+
*/
|
|
4528
|
+
const hexToRgba = (hex, isNormalize = false) => [
|
|
4529
|
+
...hexToRgb(hex, isNormalize),
|
|
4530
|
+
255 ** !isNormalize,
|
|
4531
|
+
];
|
|
4754
4532
|
|
|
4755
|
-
|
|
4533
|
+
const REGEX_IS_HEX = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i;
|
|
4756
4534
|
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
});
|
|
4535
|
+
/**
|
|
4536
|
+
* Tests if a string is a valid HEX color encoding
|
|
4537
|
+
* @param {string} hex HEX-encoded color string.
|
|
4538
|
+
* @return {boolean} If `true` the string is a valid HEX color encoding.
|
|
4539
|
+
*/
|
|
4540
|
+
const isHex = (hex) => REGEX_IS_HEX.test(hex);
|
|
4764
4541
|
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
vert: `
|
|
4772
|
-
precision highp float;
|
|
4773
|
-
attribute vec2 xy;
|
|
4774
|
-
void main () {
|
|
4775
|
-
gl_Position = vec4(xy, 0, 1);
|
|
4776
|
-
}`,
|
|
4777
|
-
frag: `
|
|
4778
|
-
precision highp float;
|
|
4779
|
-
uniform vec2 srcRes;
|
|
4780
|
-
uniform sampler2D src;
|
|
4781
|
-
uniform float gamma;
|
|
4542
|
+
/**
|
|
4543
|
+
* Tests if a number is in `[0,1]`.
|
|
4544
|
+
* @param {number} x Number to be tested.
|
|
4545
|
+
* @return {boolean} If `true` the number is in `[0,1]`.
|
|
4546
|
+
*/
|
|
4547
|
+
const isNormFloat = (x) => x >= 0 && x <= 1;
|
|
4782
4548
|
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4549
|
+
/**
|
|
4550
|
+
* Tests if an array consist of normalized numbers that are in `[0,1]` only.
|
|
4551
|
+
* @param {array} a Array to be tested
|
|
4552
|
+
* @return {boolean} If `true` the array contains only numbers in `[0,1]`.
|
|
4553
|
+
*/
|
|
4554
|
+
const isNormFloatArray = (a) => Array.isArray(a) && a.every(isNormFloat);
|
|
4786
4555
|
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
depth: { enable: false },
|
|
4801
|
-
blend: {
|
|
4802
|
-
enable: true,
|
|
4803
|
-
func: {
|
|
4804
|
-
// biome-ignore lint/style/useNamingConvention: Regl internal
|
|
4805
|
-
srcRGB: 'one',
|
|
4806
|
-
srcAlpha: 'one',
|
|
4807
|
-
// biome-ignore lint/style/useNamingConvention: Regl internal
|
|
4808
|
-
dstRGB: 'one minus src alpha',
|
|
4809
|
-
dstAlpha: 'one minus src alpha',
|
|
4810
|
-
},
|
|
4811
|
-
},
|
|
4812
|
-
});
|
|
4556
|
+
/**
|
|
4557
|
+
* Computes the cross product to determine the orientation of three points
|
|
4558
|
+
* @param {number} x1 X-coordinate of first point
|
|
4559
|
+
* @param {number} y1 Y-coordinate of first point
|
|
4560
|
+
* @param {number} x2 X-coordinate of second point
|
|
4561
|
+
* @param {number} y2 Y-coordinate of second point
|
|
4562
|
+
* @param {number} px X-coordinate of test point
|
|
4563
|
+
* @param {number} py Y-coordinate of test point
|
|
4564
|
+
* @return {number} Positive if counterclockwise, negative if clockwise
|
|
4565
|
+
*/
|
|
4566
|
+
function crossProduct(x1, y1, x2, y2, px, py) {
|
|
4567
|
+
return (x2 - x1) * (py - y1) - (px - x1) * (y2 - y1);
|
|
4568
|
+
}
|
|
4813
4569
|
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
(canvas.height - targetCanvas.height) / 2,
|
|
4824
|
-
targetCanvas.width,
|
|
4825
|
-
targetCanvas.height,
|
|
4826
|
-
0,
|
|
4827
|
-
0,
|
|
4828
|
-
targetCanvas.width,
|
|
4829
|
-
targetCanvas.height,
|
|
4830
|
-
);
|
|
4831
|
-
};
|
|
4570
|
+
/**
|
|
4571
|
+
* Determines if a point lies within a polygon using the non-zero winding rule.
|
|
4572
|
+
* This handles self-intersecting polygons and overlapping areas correctly.
|
|
4573
|
+
* @param {Array} polygon 1D list of vertices defining the polygon [x1,y1,x2,y2,...]
|
|
4574
|
+
* @param {Array} point Tuple of the form [x,y] to be tested
|
|
4575
|
+
* @return {boolean} True if point lies within the polygon
|
|
4576
|
+
*/
|
|
4577
|
+
const isPointInPolygon = (polygon, [px, py] = []) => {
|
|
4578
|
+
let winding = 0;
|
|
4832
4579
|
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
/** @type {HTMLCanvasElement} */ targetCanvas,
|
|
4839
|
-
) => {
|
|
4840
|
-
// Clear internal canvas
|
|
4841
|
-
regl.clear(CLEAR_OPTIONS);
|
|
4842
|
-
fbo.use(() => {
|
|
4843
|
-
// Clear framebuffer
|
|
4844
|
-
regl.clear(CLEAR_OPTIONS);
|
|
4845
|
-
draw();
|
|
4846
|
-
});
|
|
4847
|
-
renderToCanvas();
|
|
4848
|
-
copyTo(targetCanvas);
|
|
4849
|
-
};
|
|
4580
|
+
for (let i = 0, j = polygon.length - 2; i < polygon.length; i += 2) {
|
|
4581
|
+
const x1 = polygon[i];
|
|
4582
|
+
const y1 = polygon[i + 1];
|
|
4583
|
+
const x2 = polygon[j];
|
|
4584
|
+
const y2 = polygon[j + 1];
|
|
4850
4585
|
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4586
|
+
if (y1 <= py) {
|
|
4587
|
+
if (y2 > py) {
|
|
4588
|
+
const orientation = crossProduct(x1, y1, x2, y2, px, py);
|
|
4589
|
+
if (orientation > 0) {
|
|
4590
|
+
winding++;
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
} else if (y2 <= py) {
|
|
4594
|
+
const orientation = crossProduct(x1, y1, x2, y2, px, py);
|
|
4595
|
+
if (orientation < 0) {
|
|
4596
|
+
winding--;
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4860
4599
|
|
|
4861
|
-
|
|
4600
|
+
j = i;
|
|
4601
|
+
}
|
|
4862
4602
|
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
* frame.
|
|
4866
|
-
*/
|
|
4867
|
-
const onFrame = (/** @type {(): void} */ draw) => {
|
|
4868
|
-
drawFns.add(draw);
|
|
4869
|
-
return () => {
|
|
4870
|
-
drawFns.delete(draw);
|
|
4871
|
-
};
|
|
4872
|
-
};
|
|
4603
|
+
return winding !== 0;
|
|
4604
|
+
};
|
|
4873
4605
|
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
}
|
|
4881
|
-
});
|
|
4606
|
+
/**
|
|
4607
|
+
* Tests if a variable is a string
|
|
4608
|
+
* @param {*} s Variable to be tested
|
|
4609
|
+
* @return {boolean} If `true` variable is a string
|
|
4610
|
+
*/
|
|
4611
|
+
const isString = (s) => typeof s === 'string' || s instanceof String;
|
|
4882
4612
|
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
// screen height. The problem with too large dimensions is that at some
|
|
4890
|
-
// point WebGL will break down because there's an upper limit on how large
|
|
4891
|
-
// any buffer and texture can be. It also harms the performance quite a bit.
|
|
4892
|
-
//
|
|
4893
|
-
// By restricting the widht/height to the screen size we should have a safe
|
|
4894
|
-
// upper limit for the canvas size.
|
|
4895
|
-
//
|
|
4896
|
-
// @see
|
|
4897
|
-
// https://github.com/microsoft/vscode/issues/225808
|
|
4898
|
-
// https://github.com/flekschas/jupyter-scatter/issues/37
|
|
4899
|
-
const width =
|
|
4900
|
-
customWidth === undefined
|
|
4901
|
-
? Math.min(window.innerWidth, window.screen.availWidth)
|
|
4902
|
-
: customWidth;
|
|
4903
|
-
const height =
|
|
4904
|
-
customHeight === undefined
|
|
4905
|
-
? Math.min(window.innerHeight, window.screen.availHeight)
|
|
4906
|
-
: customHeight;
|
|
4907
|
-
canvas.width = width * window.devicePixelRatio;
|
|
4908
|
-
canvas.height = height * window.devicePixelRatio;
|
|
4909
|
-
fboRes[0] = canvas.width;
|
|
4910
|
-
fboRes[1] = canvas.height;
|
|
4911
|
-
fbo.resize(...fboRes);
|
|
4912
|
-
};
|
|
4613
|
+
/**
|
|
4614
|
+
* Tests if a number is an interger and in `[0,255]`.
|
|
4615
|
+
* @param {number} x Number to be tested.
|
|
4616
|
+
* @return {boolean} If `true` the number is an interger and in `[0,255]`.
|
|
4617
|
+
*/
|
|
4618
|
+
const isUint8 = (x) => Number.isInteger(x) && x >= 0 && x <= 255;
|
|
4913
4619
|
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4620
|
+
/**
|
|
4621
|
+
* Tests if an array consist of Uint8 numbers only.
|
|
4622
|
+
* @param {array} a Array to be tested.
|
|
4623
|
+
* @return {boolean} If `true` the array contains only Uint8 numbers.
|
|
4624
|
+
*/
|
|
4625
|
+
const isUint8Array = (a) => Array.isArray(a) && a.every(isUint8);
|
|
4917
4626
|
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4627
|
+
/**
|
|
4628
|
+
* Tests if an array is encoding an RGB color.
|
|
4629
|
+
* @param {array} rgb Array to be tested
|
|
4630
|
+
* @return {boolean} If `true` the array hold a triple of Uint8 numbers or
|
|
4631
|
+
* a triple of normalized floats.
|
|
4632
|
+
*/
|
|
4633
|
+
const isRgb = (rgb) =>
|
|
4634
|
+
rgb.length === 3 && (isNormFloatArray(rgb) || isUint8Array(rgb));
|
|
4923
4635
|
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
canvas = undefined;
|
|
4933
|
-
regl.destroy();
|
|
4934
|
-
regl = undefined;
|
|
4935
|
-
};
|
|
4636
|
+
/**
|
|
4637
|
+
* Tests if an array is encoding an RGBA color.
|
|
4638
|
+
* @param {array} rgb Array to be tested
|
|
4639
|
+
* @return {boolean} If `true` the array hold a quadruple of Uint8 numbers or
|
|
4640
|
+
* a quadruple of normalized floats.
|
|
4641
|
+
*/
|
|
4642
|
+
const isRgba = (rgba) =>
|
|
4643
|
+
rgba.length === 4 && (isNormFloatArray(rgba) || isUint8Array(rgba));
|
|
4936
4644
|
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
* Get the associated Regl instance
|
|
4947
|
-
* @return {import('regl').Regl} The associated Regl instance
|
|
4948
|
-
*/
|
|
4949
|
-
get regl() {
|
|
4950
|
-
return regl;
|
|
4951
|
-
},
|
|
4952
|
-
/**
|
|
4953
|
-
* Get the gamma value
|
|
4954
|
-
* @return {number} The gamma value
|
|
4955
|
-
*/
|
|
4956
|
-
get gamma() {
|
|
4957
|
-
return gamma;
|
|
4958
|
-
},
|
|
4959
|
-
/**
|
|
4960
|
-
* Set gamma to a new value
|
|
4961
|
-
* @param {number} newGamma - The new gamma value
|
|
4962
|
-
*/
|
|
4963
|
-
set gamma(newGamma) {
|
|
4964
|
-
gamma = +newGamma;
|
|
4965
|
-
},
|
|
4966
|
-
/**
|
|
4967
|
-
* Get whether the browser supports all necessary WebGL features
|
|
4968
|
-
* @return {boolean} If `true` the browser supports all necessary WebGL features
|
|
4969
|
-
*/
|
|
4970
|
-
get isSupported() {
|
|
4971
|
-
return isSupportingAllGlExtensions;
|
|
4972
|
-
},
|
|
4973
|
-
/**
|
|
4974
|
-
* Get whether the renderer (and its Regl instance) is destroyed
|
|
4975
|
-
* @return {boolean} If `true` the renderer is destroyed
|
|
4976
|
-
*/
|
|
4977
|
-
get isDestroyed() {
|
|
4978
|
-
return isDestroyed;
|
|
4979
|
-
},
|
|
4980
|
-
render,
|
|
4981
|
-
resize,
|
|
4982
|
-
onFrame,
|
|
4983
|
-
refresh,
|
|
4984
|
-
destroy,
|
|
4985
|
-
};
|
|
4986
|
-
};
|
|
4645
|
+
/**
|
|
4646
|
+
* Test if a color is multiple colors
|
|
4647
|
+
* @param {*} color To be tested
|
|
4648
|
+
* @return {boolean} If `true`, `color` is an array of colors.
|
|
4649
|
+
*/
|
|
4650
|
+
const isMultipleColors = (color) =>
|
|
4651
|
+
Array.isArray(color) &&
|
|
4652
|
+
color.length > 0 &&
|
|
4653
|
+
(Array.isArray(color[0]) || isString(color[0]));
|
|
4987
4654
|
|
|
4988
|
-
|
|
4989
|
-
|
|
4655
|
+
/**
|
|
4656
|
+
* Test if two arrays contain the same RGBA quadruples
|
|
4657
|
+
*/
|
|
4658
|
+
const isSameRgbas = (a, b) => {
|
|
4659
|
+
if (!(Array.isArray(a) && Array.isArray(b)) || a.length !== b.length) {
|
|
4660
|
+
return false;
|
|
4661
|
+
}
|
|
4990
4662
|
|
|
4991
|
-
|
|
4663
|
+
if (a.length === 0) {
|
|
4664
|
+
return true;
|
|
4665
|
+
}
|
|
4992
4666
|
|
|
4993
|
-
|
|
4667
|
+
// We need to test whether a and b are arrays of RGBA quadruples
|
|
4668
|
+
if (!(Array.isArray(a[0]) && Array.isArray(b[0]))) {
|
|
4669
|
+
return false;
|
|
4670
|
+
}
|
|
4994
4671
|
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4672
|
+
return a.every(([r1, g1, b1, a1], i) => {
|
|
4673
|
+
const [r2, g2, b2, a2] = b[i];
|
|
4674
|
+
return r1 === r2 && g1 === g2 && b1 === b2 && a1 === a2;
|
|
4675
|
+
});
|
|
4676
|
+
};
|
|
4999
4677
|
|
|
5000
|
-
|
|
5001
|
-
|
|
4678
|
+
/**
|
|
4679
|
+
* Fast version of `Math.max`. Based on
|
|
4680
|
+
* https://jsperf.com/math-min-max-vs-ternary-vs-if/24 `Math.max` is not
|
|
4681
|
+
* very fast
|
|
4682
|
+
* @param {number} a Value A
|
|
4683
|
+
* @param {number} b Value B
|
|
4684
|
+
* @return {boolean} If `true` A is greater than B.
|
|
4685
|
+
*/
|
|
4686
|
+
const max = (a, b) => (a > b ? a : b);
|
|
5002
4687
|
|
|
5003
|
-
|
|
4688
|
+
/**
|
|
4689
|
+
* Fast version of `Math.min`. Based on
|
|
4690
|
+
* https://jsperf.com/math-min-max-vs-ternary-vs-if/24 `Math.max` is not
|
|
4691
|
+
* very fast
|
|
4692
|
+
* @param {number} a Value A
|
|
4693
|
+
* @param {number} b Value B
|
|
4694
|
+
* @return {boolean} If `true` A is smaller than B.
|
|
4695
|
+
*/
|
|
4696
|
+
const min = (a, b) => (a < b ? a : b);
|
|
5004
4697
|
|
|
5005
|
-
|
|
4698
|
+
/**
|
|
4699
|
+
* Convert a color to an RGBA color
|
|
4700
|
+
* @param {*} color Color to be converted. Currently supports:
|
|
4701
|
+
* HEX, RGB, or RGBA.
|
|
4702
|
+
* @param {boolean} isNormalize If `true` the returned RGBA values will be
|
|
4703
|
+
* normalized to `[0,1]`.
|
|
4704
|
+
* @return {array} Quadruple defining an RGBA color.
|
|
4705
|
+
*/
|
|
4706
|
+
const toRgba = (color, shouldNormalize) => {
|
|
4707
|
+
if (isRgba(color)) {
|
|
4708
|
+
const isNormalized = isNormFloatArray(color);
|
|
4709
|
+
if (
|
|
4710
|
+
(shouldNormalize && isNormalized) ||
|
|
4711
|
+
!(shouldNormalize || isNormalized)
|
|
4712
|
+
) {
|
|
4713
|
+
return color;
|
|
4714
|
+
}
|
|
4715
|
+
if (shouldNormalize && !isNormalized) {
|
|
4716
|
+
return color.map((x) => x / 255);
|
|
4717
|
+
}
|
|
4718
|
+
return color.map((x) => x * 255);
|
|
4719
|
+
}
|
|
5006
4720
|
|
|
5007
|
-
|
|
4721
|
+
if (isRgb(color)) {
|
|
4722
|
+
const base = 255 ** !shouldNormalize;
|
|
4723
|
+
const isNormalized = isNormFloatArray(color);
|
|
5008
4724
|
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
4725
|
+
if (
|
|
4726
|
+
(shouldNormalize && isNormalized) ||
|
|
4727
|
+
!(shouldNormalize || isNormalized)
|
|
4728
|
+
) {
|
|
4729
|
+
return [...color, base];
|
|
4730
|
+
}
|
|
4731
|
+
if (shouldNormalize && !isNormalized) {
|
|
4732
|
+
return [...color.map((x) => x / 255), base];
|
|
4733
|
+
}
|
|
4734
|
+
return [...color.map((x) => x * 255), base];
|
|
4735
|
+
}
|
|
5014
4736
|
|
|
5015
|
-
|
|
4737
|
+
if (isHex(color)) {
|
|
4738
|
+
return hexToRgba(color, shouldNormalize);
|
|
4739
|
+
}
|
|
5016
4740
|
|
|
5017
|
-
|
|
4741
|
+
// biome-ignore lint/suspicious/noConsole: This is a legitimately useful warning
|
|
4742
|
+
console.warn(
|
|
4743
|
+
'Only HEX, RGB, and RGBA are handled by this function. Returning white instead.',
|
|
4744
|
+
);
|
|
4745
|
+
return shouldNormalize ? [1, 1, 1, 1] : [255, 255, 255, 255];
|
|
4746
|
+
};
|
|
5018
4747
|
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
}
|
|
5022
|
-
|
|
4748
|
+
/**
|
|
4749
|
+
* Flip the key-value pairs of an object
|
|
4750
|
+
* @param {object} obj - Object to be flipped
|
|
4751
|
+
* @return {object} Flipped object
|
|
4752
|
+
*/
|
|
4753
|
+
const flipObj = (obj) =>
|
|
4754
|
+
Object.entries(obj).reduce((out, [key, value]) => {
|
|
4755
|
+
if (out[value]) {
|
|
4756
|
+
out[value] = [...out[value], key];
|
|
4757
|
+
} else {
|
|
4758
|
+
out[value] = key;
|
|
4759
|
+
}
|
|
4760
|
+
return out;
|
|
4761
|
+
}, {});
|
|
5023
4762
|
|
|
5024
|
-
const
|
|
4763
|
+
const rgbBrightness = (rgb) =>
|
|
4764
|
+
0.21 * rgb[0] + 0.72 * rgb[1] + 0.07 * rgb[2];
|
|
5025
4765
|
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
4766
|
+
/**
|
|
4767
|
+
* Clip a number between min and max
|
|
4768
|
+
* @param {number} value - The value to be clipped
|
|
4769
|
+
* @param {number} minValue - The minimum value
|
|
4770
|
+
* @param {number} maxValue - The maximum value
|
|
4771
|
+
* @return {number} The clipped value
|
|
4772
|
+
*/
|
|
4773
|
+
const clip = (value, minValue, maxValue) =>
|
|
4774
|
+
Math.min(maxValue, Math.max(minValue, value));
|
|
5029
4775
|
|
|
5030
|
-
|
|
4776
|
+
/**
|
|
4777
|
+
* Convert object- or array-oriented points to array-oriented points
|
|
4778
|
+
* @param {import('./types').Points} points - The point data
|
|
4779
|
+
* @return {number[][]} Array-oriented points
|
|
4780
|
+
*/
|
|
4781
|
+
const toArrayOrientedPoints = (points) =>
|
|
4782
|
+
new Promise((resolve, reject) => {
|
|
4783
|
+
if (!points || Array.isArray(points)) {
|
|
4784
|
+
resolve(points);
|
|
4785
|
+
} else {
|
|
4786
|
+
const length =
|
|
4787
|
+
Array.isArray(points.x) || ArrayBuffer.isView(points.x)
|
|
4788
|
+
? points.x.length
|
|
4789
|
+
: 0;
|
|
5031
4790
|
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
4791
|
+
const getX =
|
|
4792
|
+
(Array.isArray(points.x) || ArrayBuffer.isView(points.x)) &&
|
|
4793
|
+
((i) => points.x[i]);
|
|
4794
|
+
const getY =
|
|
4795
|
+
(Array.isArray(points.y) || ArrayBuffer.isView(points.y)) &&
|
|
4796
|
+
((i) => points.y[i]);
|
|
4797
|
+
const getL =
|
|
4798
|
+
(Array.isArray(points.line) || ArrayBuffer.isView(points.line)) &&
|
|
4799
|
+
((i) => points.line[i]);
|
|
5037
4800
|
|
|
5038
|
-
|
|
5039
|
-
|
|
4801
|
+
const getLO =
|
|
4802
|
+
(Array.isArray(points.lineOrder) ||
|
|
4803
|
+
ArrayBuffer.isView(points.lineOrder)) &&
|
|
4804
|
+
((i) => points.lineOrder[i]);
|
|
5040
4805
|
|
|
5041
|
-
|
|
5042
|
-
|
|
4806
|
+
const components = Object.keys(points);
|
|
4807
|
+
const getZ = (() => {
|
|
4808
|
+
const z = components.find((c) => Z_NAMES.has(c));
|
|
4809
|
+
return (
|
|
4810
|
+
z &&
|
|
4811
|
+
(Array.isArray(points[z]) || ArrayBuffer.isView(points[z])) &&
|
|
4812
|
+
((i) => points[z][i])
|
|
4813
|
+
);
|
|
4814
|
+
})();
|
|
4815
|
+
const getW = (() => {
|
|
4816
|
+
const w = components.find((c) => W_NAMES.has(c));
|
|
4817
|
+
return (
|
|
4818
|
+
w &&
|
|
4819
|
+
(Array.isArray(points[w]) || ArrayBuffer.isView(points[w])) &&
|
|
4820
|
+
((i) => points[w][i])
|
|
4821
|
+
);
|
|
4822
|
+
})();
|
|
5043
4823
|
|
|
5044
|
-
|
|
4824
|
+
if (getX && getY && getZ && getW && getL && getLO) {
|
|
4825
|
+
resolve(
|
|
4826
|
+
points.x.map((x, i) => [
|
|
4827
|
+
x,
|
|
4828
|
+
getY(i),
|
|
4829
|
+
getZ(i),
|
|
4830
|
+
getW(i),
|
|
4831
|
+
getL(i),
|
|
4832
|
+
getLO(i),
|
|
4833
|
+
]),
|
|
4834
|
+
);
|
|
4835
|
+
} else if (getX && getY && getZ && getW && getL) {
|
|
4836
|
+
resolve(
|
|
4837
|
+
Array.from({ length }, (_, i) => [
|
|
4838
|
+
getX(i),
|
|
4839
|
+
getY(i),
|
|
4840
|
+
getZ(i),
|
|
4841
|
+
getW(i),
|
|
4842
|
+
getL(i),
|
|
4843
|
+
]),
|
|
4844
|
+
);
|
|
4845
|
+
} else if (getX && getY && getZ && getW) {
|
|
4846
|
+
resolve(
|
|
4847
|
+
Array.from({ length }, (_, i) => [
|
|
4848
|
+
getX(i),
|
|
4849
|
+
getY(i),
|
|
4850
|
+
getZ(i),
|
|
4851
|
+
getW(i),
|
|
4852
|
+
]),
|
|
4853
|
+
);
|
|
4854
|
+
} else if (getX && getY && getZ) {
|
|
4855
|
+
resolve(Array.from({ length }, (_, i) => [getX(i), getY(i), getZ(i)]));
|
|
4856
|
+
} else if (getX && getY) {
|
|
4857
|
+
resolve(Array.from({ length }, (_, i) => [getX(i), getY(i)]));
|
|
4858
|
+
} else {
|
|
4859
|
+
reject(new Error('You need to specify at least x and y'));
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
});
|
|
5045
4863
|
|
|
5046
|
-
|
|
5047
|
-
|
|
4864
|
+
const isHorizontalLine = (annotation) =>
|
|
4865
|
+
Number.isFinite(annotation.y) && !('x' in annotation);
|
|
5048
4866
|
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
particleTextureIndex = 0.5 * (1.0 + position);
|
|
4867
|
+
const isVerticalLine = (annotation) =>
|
|
4868
|
+
Number.isFinite(annotation.x) && !('y' in annotation);
|
|
5052
4869
|
|
|
5053
|
-
|
|
5054
|
-
|
|
4870
|
+
const isDomRect = (annotation) =>
|
|
4871
|
+
Number.isFinite(annotation.x) &&
|
|
4872
|
+
Number.isFinite(annotation.y) &&
|
|
4873
|
+
Number.isFinite(annotation.width) &&
|
|
4874
|
+
Number.isFinite(annotation.height);
|
|
5055
4875
|
|
|
5056
|
-
const
|
|
5057
|
-
|
|
4876
|
+
const isRect = (annotation) =>
|
|
4877
|
+
Number.isFinite(annotation.x1) &&
|
|
4878
|
+
Number.isFinite(annotation.y1) &&
|
|
4879
|
+
Number.isFinite(annotation.x2) &&
|
|
4880
|
+
Number.isFinite(annotation.x2);
|
|
5058
4881
|
|
|
5059
|
-
|
|
4882
|
+
const isPolygonAnnotation = (annotation) =>
|
|
4883
|
+
'vertices' in annotation && annotation.vertices.length > 1;
|
|
5060
4884
|
|
|
5061
|
-
|
|
5062
|
-
|
|
4885
|
+
/**
|
|
4886
|
+
* Check if an array is a valid list of 2D vertices
|
|
4887
|
+
* @param {any} arg - The argument to check
|
|
4888
|
+
* @returns {boolean} True if argument is an array of [x, y] coordinate pairs
|
|
4889
|
+
*/
|
|
4890
|
+
const isVertices = (arg) => {
|
|
4891
|
+
if (!Array.isArray(arg) || arg.length < 3) {
|
|
4892
|
+
return false;
|
|
4893
|
+
}
|
|
5063
4894
|
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
4895
|
+
for (const vertex of arg) {
|
|
4896
|
+
if (
|
|
4897
|
+
!Array.isArray(vertex) ||
|
|
4898
|
+
vertex.length !== 2 ||
|
|
4899
|
+
typeof vertex[0] !== 'number' ||
|
|
4900
|
+
typeof vertex[1] !== 'number'
|
|
4901
|
+
) {
|
|
4902
|
+
return false;
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
5067
4905
|
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
float sdf = length(c) * finalPointSize;
|
|
5071
|
-
float alpha = linearstep(finalPointSize + antiAliasing, finalPointSize - antiAliasing, sdf);
|
|
4906
|
+
return true;
|
|
4907
|
+
};
|
|
5072
4908
|
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
4909
|
+
/**
|
|
4910
|
+
* Ensure a list of vertices forms a closed polygon
|
|
4911
|
+
* @param {Array<[number, number]>} vertices - Array of [x, y] coordinates
|
|
4912
|
+
* @returns {Array<[number, number]>} Closed polygon (first vertex repeated at end if needed)
|
|
4913
|
+
*/
|
|
4914
|
+
const verticesToPolygon = (vertices) => {
|
|
4915
|
+
const polygon = [...vertices];
|
|
4916
|
+
const firstVertex = vertices.at(0);
|
|
4917
|
+
const lastVertex = vertices.at(-1);
|
|
4918
|
+
if (firstVertex[0] !== lastVertex[0] || firstVertex[1] !== lastVertex[1]) {
|
|
4919
|
+
polygon.push(firstVertex);
|
|
4920
|
+
}
|
|
4921
|
+
return polygon;
|
|
4922
|
+
};
|
|
5076
4923
|
|
|
5077
|
-
const
|
|
5078
|
-
|
|
4924
|
+
const insertionSort = (array) => {
|
|
4925
|
+
const end = array.length;
|
|
4926
|
+
for (let i = 1; i < end; i++) {
|
|
4927
|
+
// Choosing the first element in our unsorted subarray
|
|
4928
|
+
const current = array[i];
|
|
4929
|
+
// The last element of our sorted subarray
|
|
4930
|
+
let j = i - 1;
|
|
4931
|
+
while (j > -1 && current < array[j]) {
|
|
4932
|
+
array[j + 1] = array[j];
|
|
4933
|
+
j--;
|
|
4934
|
+
}
|
|
4935
|
+
array[j + 1] = current;
|
|
4936
|
+
}
|
|
4937
|
+
return array;
|
|
4938
|
+
};
|
|
5079
4939
|
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
uniform float encodingTexRes;
|
|
5089
|
-
uniform float encodingTexEps;
|
|
5090
|
-
uniform float pointSizeExtra;
|
|
5091
|
-
uniform float pointOpacityMax;
|
|
5092
|
-
uniform float pointOpacityScale;
|
|
5093
|
-
uniform float numPoints;
|
|
5094
|
-
uniform float globalState;
|
|
5095
|
-
uniform float isColoredByZ;
|
|
5096
|
-
uniform float isColoredByW;
|
|
5097
|
-
uniform float isOpacityByZ;
|
|
5098
|
-
uniform float isOpacityByW;
|
|
5099
|
-
uniform float isOpacityByDensity;
|
|
5100
|
-
uniform float isSizedByZ;
|
|
5101
|
-
uniform float isSizedByW;
|
|
5102
|
-
uniform float isPixelAligned;
|
|
5103
|
-
uniform float colorMultiplicator;
|
|
5104
|
-
uniform float opacityMultiplicator;
|
|
5105
|
-
uniform float opacityDensity;
|
|
5106
|
-
uniform float sizeMultiplicator;
|
|
5107
|
-
uniform float numColorStates;
|
|
5108
|
-
uniform float pointScale;
|
|
5109
|
-
uniform float drawingBufferWidth;
|
|
5110
|
-
uniform float drawingBufferHeight;
|
|
5111
|
-
uniform mat4 modelViewProjection;
|
|
4940
|
+
const createRenderer = (
|
|
4941
|
+
/** @type {Partial<import('./types').RendererOptions>} */ options = {},
|
|
4942
|
+
) => {
|
|
4943
|
+
let {
|
|
4944
|
+
regl,
|
|
4945
|
+
canvas = document.createElement('canvas'),
|
|
4946
|
+
gamma = DEFAULT_GAMMA,
|
|
4947
|
+
} = options;
|
|
5112
4948
|
|
|
5113
|
-
|
|
4949
|
+
let isDestroyed = false;
|
|
5114
4950
|
|
|
5115
|
-
|
|
5116
|
-
|
|
4951
|
+
if (!regl) {
|
|
4952
|
+
regl = createRegl(canvas);
|
|
4953
|
+
}
|
|
5117
4954
|
|
|
5118
|
-
|
|
5119
|
-
vec4 state = texture2D(stateTex, stateIndex);
|
|
4955
|
+
const isSupportingAllGlExtensions = checkReglExtensions(regl);
|
|
5120
4956
|
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
vec2 snappedPosition = (pixelPos / vec2(drawingBufferWidth, drawingBufferHeight)) * 2.0 - 1.0;
|
|
5129
|
-
gl_Position = vec4(snappedPosition, 0.0, 1.0);
|
|
5130
|
-
}
|
|
4957
|
+
const fboRes = [canvas.width, canvas.height];
|
|
4958
|
+
const fbo = regl.framebuffer({
|
|
4959
|
+
width: fboRes[0],
|
|
4960
|
+
height: fboRes[1],
|
|
4961
|
+
colorFormat: 'rgba',
|
|
4962
|
+
colorType: 'float',
|
|
4963
|
+
});
|
|
5131
4964
|
|
|
4965
|
+
/**
|
|
4966
|
+
* Render the float32 framebuffer to the internal canvas
|
|
4967
|
+
*
|
|
4968
|
+
* From https://observablehq.com/@rreusser/selecting-the-right-opacity-for-2d-point-clouds
|
|
4969
|
+
*/
|
|
4970
|
+
const renderToCanvas = regl({
|
|
4971
|
+
vert: `
|
|
4972
|
+
precision highp float;
|
|
4973
|
+
attribute vec2 xy;
|
|
4974
|
+
void main () {
|
|
4975
|
+
gl_Position = vec4(xy, 0, 1);
|
|
4976
|
+
}`,
|
|
4977
|
+
frag: `
|
|
4978
|
+
precision highp float;
|
|
4979
|
+
uniform vec2 srcRes;
|
|
4980
|
+
uniform sampler2D src;
|
|
4981
|
+
uniform float gamma;
|
|
5132
4982
|
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
4983
|
+
vec3 approxLinearToSRGB (vec3 rgb, float gamma) {
|
|
4984
|
+
return pow(clamp(rgb, vec3(0), vec3(1)), vec3(1.0 / gamma));
|
|
4985
|
+
}
|
|
5136
4986
|
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
4987
|
+
void main () {
|
|
4988
|
+
vec4 color = texture2D(src, gl_FragCoord.xy / srcRes);
|
|
4989
|
+
gl_FragColor = vec4(approxLinearToSRGB(color.rgb, gamma), color.a);
|
|
4990
|
+
}`,
|
|
4991
|
+
attributes: {
|
|
4992
|
+
xy: [-4, -4, 4, -4, 0, 4],
|
|
4993
|
+
},
|
|
4994
|
+
uniforms: {
|
|
4995
|
+
src: () => fbo,
|
|
4996
|
+
srcRes: () => fboRes,
|
|
4997
|
+
gamma: () => gamma,
|
|
4998
|
+
},
|
|
4999
|
+
count: 3,
|
|
5000
|
+
depth: { enable: false },
|
|
5001
|
+
blend: {
|
|
5002
|
+
enable: true,
|
|
5003
|
+
func: {
|
|
5004
|
+
srcRGB: 'one',
|
|
5005
|
+
srcAlpha: 'one',
|
|
5006
|
+
dstRGB: 'one minus src alpha',
|
|
5007
|
+
dstAlpha: 'one minus src alpha',
|
|
5008
|
+
},
|
|
5009
|
+
},
|
|
5010
|
+
});
|
|
5140
5011
|
|
|
5141
|
-
|
|
5142
|
-
|
|
5012
|
+
/**
|
|
5013
|
+
* Copy the pixels from the internal canvas onto the target canvas
|
|
5014
|
+
*/
|
|
5015
|
+
const copyTo = (targetCanvas) => {
|
|
5016
|
+
const ctx = targetCanvas.getContext('2d');
|
|
5017
|
+
ctx.clearRect(0, 0, targetCanvas.width, targetCanvas.height);
|
|
5018
|
+
ctx.drawImage(
|
|
5019
|
+
canvas,
|
|
5020
|
+
(canvas.width - targetCanvas.width) / 2,
|
|
5021
|
+
(canvas.height - targetCanvas.height) / 2,
|
|
5022
|
+
targetCanvas.width,
|
|
5023
|
+
targetCanvas.height,
|
|
5024
|
+
0,
|
|
5025
|
+
0,
|
|
5026
|
+
targetCanvas.width,
|
|
5027
|
+
targetCanvas.height,
|
|
5028
|
+
);
|
|
5029
|
+
};
|
|
5143
5030
|
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5031
|
+
/**
|
|
5032
|
+
* The render function
|
|
5033
|
+
*/
|
|
5034
|
+
const render = (
|
|
5035
|
+
/** @type {(): void} */ draw,
|
|
5036
|
+
/** @type {HTMLCanvasElement} */ targetCanvas,
|
|
5037
|
+
) => {
|
|
5038
|
+
// Clear internal canvas
|
|
5039
|
+
regl.clear(CLEAR_OPTIONS);
|
|
5040
|
+
fbo.use(() => {
|
|
5041
|
+
// Clear framebuffer
|
|
5042
|
+
regl.clear(CLEAR_OPTIONS);
|
|
5043
|
+
draw();
|
|
5044
|
+
});
|
|
5045
|
+
renderToCanvas();
|
|
5046
|
+
copyTo(targetCanvas);
|
|
5047
|
+
};
|
|
5148
5048
|
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5049
|
+
/**
|
|
5050
|
+
* Update Regl's viewport, drawingBufferWidth, and drawingBufferHeight
|
|
5051
|
+
*
|
|
5052
|
+
* @description Call this method after the viewport has changed, e.g., width
|
|
5053
|
+
* or height have been altered
|
|
5054
|
+
*/
|
|
5055
|
+
const refresh = () => {
|
|
5056
|
+
regl.poll();
|
|
5057
|
+
};
|
|
5153
5058
|
|
|
5154
|
-
|
|
5059
|
+
const drawFns = new Set();
|
|
5155
5060
|
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5061
|
+
/**
|
|
5062
|
+
* Register an draw function that is going to be invoked on every animation
|
|
5063
|
+
* frame.
|
|
5064
|
+
*/
|
|
5065
|
+
const onFrame = (/** @type {(): void} */ draw) => {
|
|
5066
|
+
drawFns.add(draw);
|
|
5067
|
+
return () => {
|
|
5068
|
+
drawFns.delete(draw);
|
|
5069
|
+
};
|
|
5070
|
+
};
|
|
5160
5071
|
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5072
|
+
const frame = regl.frame(() => {
|
|
5073
|
+
const iterator = drawFns.values();
|
|
5074
|
+
let result = iterator.next();
|
|
5075
|
+
while (!result.done) {
|
|
5076
|
+
result.value(); // The draw function
|
|
5077
|
+
result = iterator.next();
|
|
5078
|
+
}
|
|
5079
|
+
});
|
|
5167
5080
|
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5081
|
+
const resize = (
|
|
5082
|
+
/** @type {number} */ customWidth,
|
|
5083
|
+
/** @type {number} */ customHeight,
|
|
5084
|
+
) => {
|
|
5085
|
+
const width = customWidth == null ? window.innerWidth : customWidth;
|
|
5086
|
+
const height = customHeight == null ? window.innerHeight : customHeight;
|
|
5087
|
+
canvas.width = width * window.devicePixelRatio;
|
|
5088
|
+
canvas.height = height * window.devicePixelRatio;
|
|
5089
|
+
fboRes[0] = canvas.width;
|
|
5090
|
+
fboRes[1] = canvas.height;
|
|
5091
|
+
fbo.resize(...fboRes);
|
|
5092
|
+
};
|
|
5173
5093
|
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
float opacityIndexZ = isOpacityByZ * floor(state.z * opacityMultiplicator);
|
|
5178
|
-
float opacityIndexW = isOpacityByW * floor(state.w * opacityMultiplicator);
|
|
5179
|
-
float opacityIndex = opacityIndexZ + opacityIndexW;
|
|
5094
|
+
const resizeHandler = () => {
|
|
5095
|
+
resize();
|
|
5096
|
+
};
|
|
5180
5097
|
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
);
|
|
5186
|
-
color.a = texture2D(encodingTex, opacityTexIndex)[${1 + globalState}];
|
|
5187
|
-
} else {
|
|
5188
|
-
color.a = min(1.0, opacityDensity + globalState);
|
|
5189
|
-
}
|
|
5190
|
-
`;
|
|
5191
|
-
})()
|
|
5098
|
+
if (!options.canvas) {
|
|
5099
|
+
window.addEventListener('resize', resizeHandler);
|
|
5100
|
+
window.addEventListener('orientationchange', resizeHandler);
|
|
5101
|
+
resize();
|
|
5192
5102
|
}
|
|
5193
5103
|
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5104
|
+
/**
|
|
5105
|
+
* Destroy the renderer to free resources and cancel animation frames
|
|
5106
|
+
*/
|
|
5107
|
+
const destroy = () => {
|
|
5108
|
+
isDestroyed = true;
|
|
5109
|
+
window.removeEventListener('resize', resizeHandler);
|
|
5110
|
+
window.removeEventListener('orientationchange', resizeHandler);
|
|
5111
|
+
frame.cancel();
|
|
5112
|
+
canvas = undefined;
|
|
5113
|
+
regl.destroy();
|
|
5114
|
+
regl = undefined;
|
|
5115
|
+
};
|
|
5116
|
+
|
|
5117
|
+
return {
|
|
5118
|
+
/**
|
|
5119
|
+
* Get the associated canvas element
|
|
5120
|
+
* @return {HTMLCanvasElement} The associated canvas element
|
|
5121
|
+
*/
|
|
5122
|
+
get canvas() {
|
|
5123
|
+
return canvas;
|
|
5124
|
+
},
|
|
5125
|
+
/**
|
|
5126
|
+
* Get the associated Regl instance
|
|
5127
|
+
* @return {import('regl').Regl} The associated Regl instance
|
|
5128
|
+
*/
|
|
5129
|
+
get regl() {
|
|
5130
|
+
return regl;
|
|
5131
|
+
},
|
|
5132
|
+
/**
|
|
5133
|
+
* Get the gamma value
|
|
5134
|
+
* @return {number} The gamma value
|
|
5135
|
+
*/
|
|
5136
|
+
get gamma() {
|
|
5137
|
+
return gamma;
|
|
5138
|
+
},
|
|
5139
|
+
/**
|
|
5140
|
+
* Set gamma to a new value
|
|
5141
|
+
* @param {number} newGamma - The new gamma value
|
|
5142
|
+
*/
|
|
5143
|
+
set gamma(newGamma) {
|
|
5144
|
+
gamma = +newGamma;
|
|
5145
|
+
},
|
|
5146
|
+
/**
|
|
5147
|
+
* Get whether the browser supports all necessary WebGL features
|
|
5148
|
+
* @return {boolean} If `true` the browser supports all necessary WebGL features
|
|
5149
|
+
*/
|
|
5150
|
+
get isSupported() {
|
|
5151
|
+
return isSupportingAllGlExtensions;
|
|
5152
|
+
},
|
|
5153
|
+
/**
|
|
5154
|
+
* Get whether the renderer (and its Regl instance) is destroyed
|
|
5155
|
+
* @return {boolean} If `true` the renderer is destroyed
|
|
5156
|
+
*/
|
|
5157
|
+
get isDestroyed() {
|
|
5158
|
+
return isDestroyed;
|
|
5159
|
+
},
|
|
5160
|
+
render,
|
|
5161
|
+
resize,
|
|
5162
|
+
onFrame,
|
|
5163
|
+
refresh,
|
|
5164
|
+
destroy,
|
|
5165
|
+
};
|
|
5166
|
+
};
|
|
5199
5167
|
|
|
5200
5168
|
/* eslint-env worker */
|
|
5201
5169
|
/* eslint no-restricted-globals: 1 */
|
|
@@ -5304,7 +5272,6 @@ const worker = function worker() {
|
|
|
5304
5272
|
* @param {[type]} simplified [description]
|
|
5305
5273
|
* @return {[type]} [description]
|
|
5306
5274
|
*/
|
|
5307
|
-
// biome-ignore lint/style/useNamingConvention: DP stands for Douglas Peucker
|
|
5308
5275
|
const simplifyDPStep = (points, first, last, tolerance, simplified) => {
|
|
5309
5276
|
let maxDist = tolerance;
|
|
5310
5277
|
let index;
|
|
@@ -5412,7 +5379,6 @@ const worker = function worker() {
|
|
|
5412
5379
|
const groupedPoints = {};
|
|
5413
5380
|
|
|
5414
5381
|
const isOrdered = !Number.isNaN(+points[0][5]);
|
|
5415
|
-
// biome-ignore lint/complexity/noForEach: somehow for .. of does not work in a worker
|
|
5416
5382
|
points.forEach((point) => {
|
|
5417
5383
|
const segId = point[4];
|
|
5418
5384
|
|
|
@@ -5428,7 +5394,6 @@ const worker = function worker() {
|
|
|
5428
5394
|
});
|
|
5429
5395
|
|
|
5430
5396
|
// The filtering ensures that non-existing array entries are removed
|
|
5431
|
-
// biome-ignore lint/complexity/noForEach: somehow for .. of does not work in a worker
|
|
5432
5397
|
Object.entries(groupedPoints).forEach((idPoints) => {
|
|
5433
5398
|
groupedPoints[idPoints[0]] = idPoints[1].filter((v) => v);
|
|
5434
5399
|
// Store the first point as the reference
|
|
@@ -5485,8 +5450,6 @@ const createSplineCurve = (
|
|
|
5485
5450
|
worker$1.postMessage({ points, options });
|
|
5486
5451
|
});
|
|
5487
5452
|
|
|
5488
|
-
var version = "1.14.1+1.0.1";
|
|
5489
|
-
|
|
5490
5453
|
const deprecations = {
|
|
5491
5454
|
showRecticle: {
|
|
5492
5455
|
replacement: 'showReticle',
|
|
@@ -5595,8 +5558,8 @@ const createScatterplot = (
|
|
|
5595
5558
|
deselectOnEscape = DEFAULT_DESELECT_ON_ESCAPE,
|
|
5596
5559
|
lassoColor = DEFAULT_LASSO_COLOR,
|
|
5597
5560
|
lassoLineWidth = DEFAULT_LASSO_LINE_WIDTH,
|
|
5598
|
-
lassoMinDelay = DEFAULT_LASSO_MIN_DELAY,
|
|
5599
|
-
lassoMinDist = DEFAULT_LASSO_MIN_DIST,
|
|
5561
|
+
lassoMinDelay = DEFAULT_LASSO_MIN_DELAY$1,
|
|
5562
|
+
lassoMinDist = DEFAULT_LASSO_MIN_DIST$1,
|
|
5600
5563
|
lassoMode = DEFAULT_LASSO_MODE,
|
|
5601
5564
|
lassoClearEvent = DEFAULT_LASSO_CLEAR_EVENT,
|
|
5602
5565
|
lassoInitiator = DEFAULT_LASSO_INITIATOR,
|
|
@@ -5607,7 +5570,7 @@ const createScatterplot = (
|
|
|
5607
5570
|
lassoLongPressAfterEffectTime = DEFAULT_LASSO_LONG_PRESS_AFTER_EFFECT_TIME,
|
|
5608
5571
|
lassoLongPressEffectDelay = DEFAULT_LASSO_LONG_PRESS_EFFECT_DELAY,
|
|
5609
5572
|
lassoLongPressRevertEffectTime = DEFAULT_LASSO_LONG_PRESS_REVERT_EFFECT_TIME,
|
|
5610
|
-
lassoType = DEFAULT_LASSO_TYPE,
|
|
5573
|
+
lassoType = DEFAULT_LASSO_TYPE$1,
|
|
5611
5574
|
lassoBrushSize = DEFAULT_LASSO_BRUSH_SIZE,
|
|
5612
5575
|
actionKeyMap = DEFAULT_ACTION_KEY_MAP,
|
|
5613
5576
|
mouseMode = DEFAULT_MOUSE_MODE,
|
|
@@ -5639,6 +5602,7 @@ const createScatterplot = (
|
|
|
5639
5602
|
opacityInactiveMax = DEFAULT_OPACITY_INACTIVE_MAX,
|
|
5640
5603
|
opacityInactiveScale = DEFAULT_OPACITY_INACTIVE_SCALE,
|
|
5641
5604
|
sizeBy = DEFAULT_SIZE_BY,
|
|
5605
|
+
pointOrder = DEFAULT_POINT_ORDER,
|
|
5642
5606
|
pointScaleMode = DEFAULT_POINT_SCALE_MODE,
|
|
5643
5607
|
height = DEFAULT_HEIGHT,
|
|
5644
5608
|
width = DEFAULT_WIDTH,
|
|
@@ -5714,9 +5678,7 @@ const createScatterplot = (
|
|
|
5714
5678
|
let pointConnections;
|
|
5715
5679
|
let pointConnectionMap;
|
|
5716
5680
|
let computingPointConnectionCurves;
|
|
5717
|
-
// biome-ignore lint/style/useNamingConvention: HLine stands for HorizontalLine
|
|
5718
5681
|
let reticleHLine;
|
|
5719
|
-
// biome-ignore lint/style/useNamingConvention: VLine stands for VerticalLine
|
|
5720
5682
|
let reticleVLine;
|
|
5721
5683
|
let computedPointSizeMouseDetection;
|
|
5722
5684
|
let lassoInitiatorTimeout;
|
|
@@ -5849,6 +5811,8 @@ const createScatterplot = (
|
|
|
5849
5811
|
let normalPointsIndexBuffer; // Buffer holding the indices pointing to the correct texel
|
|
5850
5812
|
let selectedPointsIndexBuffer; // Used for pointing to the selected texels
|
|
5851
5813
|
let hoveredPointIndexBuffer; // Used for pointing to the hovered texels
|
|
5814
|
+
let pointOrderIndices = null; // Validated ordered point indices (Int32Array or null)
|
|
5815
|
+
let pointOrderIndex = null; // Float32Array of tex coords in pointOrder sequence
|
|
5852
5816
|
|
|
5853
5817
|
let cameraZoomTargetStart; // Stores the start (i.e., current) camera target for zooming
|
|
5854
5818
|
let cameraZoomTargetEnd; // Stores the end camera target for zooming
|
|
@@ -5871,9 +5835,7 @@ const createScatterplot = (
|
|
|
5871
5835
|
let isAnnotationsDrawn = false;
|
|
5872
5836
|
let isMouseOverCanvasChecked = false;
|
|
5873
5837
|
|
|
5874
|
-
// biome-ignore lint/style/useNamingConvention: ZDate is not one word
|
|
5875
5838
|
let valueZDataType = CATEGORICAL;
|
|
5876
|
-
// biome-ignore lint/style/useNamingConvention: WDate is not one word
|
|
5877
5839
|
let valueWDataType = CATEGORICAL;
|
|
5878
5840
|
|
|
5879
5841
|
/** @type{number|undefined} */
|
|
@@ -5947,9 +5909,7 @@ const createScatterplot = (
|
|
|
5947
5909
|
return points;
|
|
5948
5910
|
};
|
|
5949
5911
|
|
|
5950
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
5951
5912
|
const getPointsInBBox = (x0, y0, x1, y1) => {
|
|
5952
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
5953
5913
|
const pointsInBBox = spatialIndex.range(x0, y0, x1, y1);
|
|
5954
5914
|
if (isPointsFiltered) {
|
|
5955
5915
|
return pointsInBBox.filter((i) => filteredPointsSet.has(i));
|
|
@@ -5964,7 +5924,6 @@ const createScatterplot = (
|
|
|
5964
5924
|
const pointSizeNdc = getPointSizeNdc(4);
|
|
5965
5925
|
|
|
5966
5926
|
// Get all points within a close range
|
|
5967
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
5968
5927
|
const pointsInBBox = getPointsInBBox(
|
|
5969
5928
|
xNdc - pointSizeNdc,
|
|
5970
5929
|
yNdc - pointSizeNdc,
|
|
@@ -6002,7 +5961,6 @@ const createScatterplot = (
|
|
|
6002
5961
|
}
|
|
6003
5962
|
|
|
6004
5963
|
// ...to efficiently preselect potentially selected points
|
|
6005
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
6006
5964
|
const pointsInBBox = getPointsInBBox(...bBox);
|
|
6007
5965
|
// next we test each point in the bounding box if it is in the polygon too
|
|
6008
5966
|
const pointsInPolygon = [];
|
|
@@ -6107,6 +6065,40 @@ const createScatterplot = (
|
|
|
6107
6065
|
}
|
|
6108
6066
|
};
|
|
6109
6067
|
|
|
6068
|
+
/**
|
|
6069
|
+
* Convert vertices from data space to GL space
|
|
6070
|
+
* @param {Array<[number, number]>} vertices - Vertices in data space
|
|
6071
|
+
* @returns {number[] | null} Flat array of GL coordinates or null if scales not defined
|
|
6072
|
+
*/
|
|
6073
|
+
const verticesFromDataToGl = (vertices) => {
|
|
6074
|
+
// Check if xScale/yScale are defined
|
|
6075
|
+
if (!(xScale && yScale)) {
|
|
6076
|
+
// biome-ignore lint/suspicious/noConsole: User warning for missing configuration
|
|
6077
|
+
console.warn(
|
|
6078
|
+
'xScale and yScale must be defined for programmatic lasso selection',
|
|
6079
|
+
);
|
|
6080
|
+
return null;
|
|
6081
|
+
}
|
|
6082
|
+
|
|
6083
|
+
const verticesGl = [];
|
|
6084
|
+
for (const [x, y] of vertices) {
|
|
6085
|
+
// Step 1: Data space → normalized [0, 1]
|
|
6086
|
+
const xNorm = (x - xDomainStart) / xDomainSize;
|
|
6087
|
+
const yNorm = (y - yDomainStart) / yDomainSize;
|
|
6088
|
+
|
|
6089
|
+
// Step 2: Normalized [0, 1] → NDC [-1, 1]
|
|
6090
|
+
const xNdc = xNorm * 2 - 1;
|
|
6091
|
+
const yNdc = yNorm * 2 - 1;
|
|
6092
|
+
|
|
6093
|
+
// Step 3: NDC → GL space (camera transform)
|
|
6094
|
+
const [xGl, yGl] = getScatterGlPos(xNdc, yNdc);
|
|
6095
|
+
|
|
6096
|
+
verticesGl.push(xGl, yGl);
|
|
6097
|
+
}
|
|
6098
|
+
|
|
6099
|
+
return verticesGl;
|
|
6100
|
+
};
|
|
6101
|
+
|
|
6110
6102
|
/**
|
|
6111
6103
|
* Select and highlight a set of points
|
|
6112
6104
|
* @param {number | number[]} pointIdxs
|
|
@@ -6204,12 +6196,53 @@ const createScatterplot = (
|
|
|
6204
6196
|
setPointConnectionColorState(selectedPoints, 1);
|
|
6205
6197
|
|
|
6206
6198
|
if (!preventEvent) {
|
|
6207
|
-
pubSub.publish('select', { points: selectedPoints });
|
|
6199
|
+
pubSub.publish('select', { points: selectedPoints, lassoMode });
|
|
6208
6200
|
}
|
|
6209
6201
|
|
|
6210
6202
|
draw = true;
|
|
6211
6203
|
};
|
|
6212
6204
|
|
|
6205
|
+
/**
|
|
6206
|
+
* Lasso a certain area and select contained points
|
|
6207
|
+
* @param {[number, number][]} vertices - Lasso vertices in either data space (default) or GL space
|
|
6208
|
+
* @param {import('./types').ScatterplotMethodOptions['lasso']}
|
|
6209
|
+
*/
|
|
6210
|
+
const lassoSelect = (
|
|
6211
|
+
vertices,
|
|
6212
|
+
{ merge = false, remove = false, isGl = false } = {},
|
|
6213
|
+
) => {
|
|
6214
|
+
if (!isVertices(vertices)) {
|
|
6215
|
+
throw new Error(
|
|
6216
|
+
'Lasso selection requires at least 3 vertices as [x, y] coordinate pairs',
|
|
6217
|
+
);
|
|
6218
|
+
}
|
|
6219
|
+
|
|
6220
|
+
const closedPolygon = verticesToPolygon(vertices);
|
|
6221
|
+
|
|
6222
|
+
let polygonGl;
|
|
6223
|
+
let polygonGlFlat;
|
|
6224
|
+
|
|
6225
|
+
if (isGl) {
|
|
6226
|
+
polygonGl = closedPolygon;
|
|
6227
|
+
polygonGlFlat = closedPolygon.flat();
|
|
6228
|
+
} else {
|
|
6229
|
+
polygonGlFlat = verticesFromDataToGl(closedPolygon);
|
|
6230
|
+
|
|
6231
|
+
if (!polygonGlFlat) {
|
|
6232
|
+
throw new Error(
|
|
6233
|
+
'xScale and yScale must be defined to convert lasso vertices from data space to GL space',
|
|
6234
|
+
);
|
|
6235
|
+
}
|
|
6236
|
+
|
|
6237
|
+
polygonGl = [];
|
|
6238
|
+
for (let i = 0; i < polygonGlFlat.length; i += 2) {
|
|
6239
|
+
polygonGl.push([polygonGlFlat[i], polygonGlFlat[i + 1]]);
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
|
|
6243
|
+
lassoEnd(polygonGl, polygonGlFlat, { merge, remove });
|
|
6244
|
+
};
|
|
6245
|
+
|
|
6213
6246
|
/**
|
|
6214
6247
|
* @param {number} point
|
|
6215
6248
|
* @param {import('./types').ScatterplotMethodOptions['hover']} options
|
|
@@ -6886,6 +6919,44 @@ const createScatterplot = (
|
|
|
6886
6919
|
const setSizeBy = (type) => {
|
|
6887
6920
|
sizeBy = getEncodingType(type, DEFAULT_SIZE_BY);
|
|
6888
6921
|
};
|
|
6922
|
+
const setPointOrder = (newPointOrder) => {
|
|
6923
|
+
if (newPointOrder === null || newPointOrder === undefined) {
|
|
6924
|
+
pointOrder = null;
|
|
6925
|
+
} else if (Array.isArray(newPointOrder) || ArrayBuffer.isView(newPointOrder)) {
|
|
6926
|
+
pointOrder = newPointOrder;
|
|
6927
|
+
} else {
|
|
6928
|
+
return;
|
|
6929
|
+
}
|
|
6930
|
+
|
|
6931
|
+
if (isPointsDrawn) {
|
|
6932
|
+
computePointOrderIndex();
|
|
6933
|
+
if (isPointsFiltered) {
|
|
6934
|
+
// Re-apply filter respecting the new point order
|
|
6935
|
+
const filteredPointsBuffer = [];
|
|
6936
|
+
if (pointOrderIndices !== null) {
|
|
6937
|
+
for (let i = 0; i < pointOrderIndices.length; i++) {
|
|
6938
|
+
if (filteredPointsSet.has(pointOrderIndices[i])) {
|
|
6939
|
+
filteredPointsBuffer.push.apply(
|
|
6940
|
+
filteredPointsBuffer,
|
|
6941
|
+
indexToStateTexCoord(pointOrderIndices[i]),
|
|
6942
|
+
);
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6945
|
+
} else {
|
|
6946
|
+
const sortedFiltered = insertionSort([...filteredPointsSet]);
|
|
6947
|
+
for (const idx of sortedFiltered) {
|
|
6948
|
+
filteredPointsBuffer.push.apply(
|
|
6949
|
+
filteredPointsBuffer,
|
|
6950
|
+
indexToStateTexCoord(idx),
|
|
6951
|
+
);
|
|
6952
|
+
}
|
|
6953
|
+
}
|
|
6954
|
+
normalPointsIndexBuffer.subdata(filteredPointsBuffer);
|
|
6955
|
+
} else {
|
|
6956
|
+
normalPointsIndexBuffer.subdata(getEffectivePointIndex(numPoints));
|
|
6957
|
+
}
|
|
6958
|
+
}
|
|
6959
|
+
};
|
|
6889
6960
|
const setPointConnectionColorBy = (type) => {
|
|
6890
6961
|
pointConnectionColorBy = getEncodingType(
|
|
6891
6962
|
type,
|
|
@@ -7053,16 +7124,14 @@ const createScatterplot = (
|
|
|
7053
7124
|
getPointOpacityScale = getPointOpacityScaleBase,
|
|
7054
7125
|
) =>
|
|
7055
7126
|
renderer.regl({
|
|
7056
|
-
frag: renderPointsAsSquares ? FRAGMENT_SHADER
|
|
7127
|
+
frag: renderPointsAsSquares ? FRAGMENT_SHADER : FRAGMENT_SHADER$1,
|
|
7057
7128
|
vert: createVertexShader(globalState),
|
|
7058
7129
|
|
|
7059
7130
|
blend: {
|
|
7060
7131
|
enable: !disableAlphaBlending,
|
|
7061
7132
|
func: {
|
|
7062
|
-
// biome-ignore lint/style/useNamingConvention: Regl specific
|
|
7063
7133
|
srcRGB: 'src alpha',
|
|
7064
7134
|
srcAlpha: 'one',
|
|
7065
|
-
// biome-ignore lint/style/useNamingConvention: Regl specific
|
|
7066
7135
|
dstRGB: 'one minus src alpha',
|
|
7067
7136
|
dstAlpha: 'one minus src alpha',
|
|
7068
7137
|
},
|
|
@@ -7203,10 +7272,8 @@ const createScatterplot = (
|
|
|
7203
7272
|
blend: {
|
|
7204
7273
|
enable: true,
|
|
7205
7274
|
func: {
|
|
7206
|
-
// biome-ignore lint/style/useNamingConvention: Regl specific
|
|
7207
7275
|
srcRGB: 'src alpha',
|
|
7208
7276
|
srcAlpha: 'one',
|
|
7209
|
-
// biome-ignore lint/style/useNamingConvention: Regl specific
|
|
7210
7277
|
dstRGB: 'one minus src alpha',
|
|
7211
7278
|
dstAlpha: 'one minus src alpha',
|
|
7212
7279
|
},
|
|
@@ -7284,6 +7351,51 @@ const createScatterplot = (
|
|
|
7284
7351
|
return index;
|
|
7285
7352
|
};
|
|
7286
7353
|
|
|
7354
|
+
const computePointOrderIndex = () => {
|
|
7355
|
+
if (pointOrder === null) {
|
|
7356
|
+
pointOrderIndices = null;
|
|
7357
|
+
pointOrderIndex = null;
|
|
7358
|
+
return;
|
|
7359
|
+
}
|
|
7360
|
+
|
|
7361
|
+
const includedSet = new Set();
|
|
7362
|
+
const orderedIndices = [];
|
|
7363
|
+
|
|
7364
|
+
for (let i = 0; i < pointOrder.length; i++) {
|
|
7365
|
+
const idx = pointOrder[i];
|
|
7366
|
+
if (
|
|
7367
|
+
Number.isFinite(idx) &&
|
|
7368
|
+
idx >= 0 &&
|
|
7369
|
+
idx < numPoints &&
|
|
7370
|
+
!includedSet.has(idx)
|
|
7371
|
+
) {
|
|
7372
|
+
orderedIndices.push(idx);
|
|
7373
|
+
includedSet.add(idx);
|
|
7374
|
+
}
|
|
7375
|
+
}
|
|
7376
|
+
|
|
7377
|
+
// Append any missing indices in sequential order
|
|
7378
|
+
for (let i = 0; i < numPoints; i++) {
|
|
7379
|
+
if (!includedSet.has(i)) {
|
|
7380
|
+
orderedIndices.push(i);
|
|
7381
|
+
}
|
|
7382
|
+
}
|
|
7383
|
+
|
|
7384
|
+
pointOrderIndices = orderedIndices;
|
|
7385
|
+
|
|
7386
|
+
pointOrderIndex = new Float32Array(orderedIndices.length * 2);
|
|
7387
|
+
let j = 0;
|
|
7388
|
+
for (let i = 0; i < orderedIndices.length; i++) {
|
|
7389
|
+
const texCoord = indexToStateTexCoord(orderedIndices[i]);
|
|
7390
|
+
pointOrderIndex[j] = texCoord[0];
|
|
7391
|
+
pointOrderIndex[j + 1] = texCoord[1];
|
|
7392
|
+
j += 2;
|
|
7393
|
+
}
|
|
7394
|
+
};
|
|
7395
|
+
|
|
7396
|
+
const getEffectivePointIndex = (count) =>
|
|
7397
|
+
pointOrderIndex !== null ? pointOrderIndex : createPointIndex(count);
|
|
7398
|
+
|
|
7287
7399
|
const createStateTexture = (newPoints, dataTypes = {}) => {
|
|
7288
7400
|
const numNewPoints = newPoints.length;
|
|
7289
7401
|
stateTexRes = Math.max(2, Math.ceil(Math.sqrt(numNewPoints)));
|
|
@@ -7375,9 +7487,18 @@ const createScatterplot = (
|
|
|
7375
7487
|
const preventFilterReset =
|
|
7376
7488
|
options?.preventFilterReset && newPoints.length === numPoints;
|
|
7377
7489
|
|
|
7490
|
+
const prevNumPoints = numPoints;
|
|
7378
7491
|
numPoints = newPoints.length;
|
|
7379
7492
|
numPointsInView = numPoints;
|
|
7380
7493
|
|
|
7494
|
+
// Reset pointOrder when re-drawing with different-length data
|
|
7495
|
+
// (ordering becomes meaningless). Skip on first draw (prevNumPoints === 0).
|
|
7496
|
+
if (prevNumPoints > 0 && numPoints !== prevNumPoints) {
|
|
7497
|
+
pointOrder = null;
|
|
7498
|
+
pointOrderIndices = null;
|
|
7499
|
+
pointOrderIndex = null;
|
|
7500
|
+
}
|
|
7501
|
+
|
|
7381
7502
|
if (stateTex) {
|
|
7382
7503
|
stateTex.destroy();
|
|
7383
7504
|
}
|
|
@@ -7387,10 +7508,11 @@ const createScatterplot = (
|
|
|
7387
7508
|
});
|
|
7388
7509
|
|
|
7389
7510
|
if (!preventFilterReset) {
|
|
7511
|
+
computePointOrderIndex();
|
|
7390
7512
|
normalPointsIndexBuffer({
|
|
7391
7513
|
usage: 'static',
|
|
7392
7514
|
type: 'float',
|
|
7393
|
-
data:
|
|
7515
|
+
data: getEffectivePointIndex(numPoints),
|
|
7394
7516
|
});
|
|
7395
7517
|
}
|
|
7396
7518
|
|
|
@@ -7631,7 +7753,7 @@ const createScatterplot = (
|
|
|
7631
7753
|
const unfilter = ({ preventEvent = false } = {}) => {
|
|
7632
7754
|
isPointsFiltered = false;
|
|
7633
7755
|
filteredPointsSet.clear();
|
|
7634
|
-
normalPointsIndexBuffer.subdata(
|
|
7756
|
+
normalPointsIndexBuffer.subdata(getEffectivePointIndex(numPoints));
|
|
7635
7757
|
|
|
7636
7758
|
return new Promise((resolve) => {
|
|
7637
7759
|
const finish = () => {
|
|
@@ -7690,9 +7812,20 @@ const createScatterplot = (
|
|
|
7690
7812
|
}
|
|
7691
7813
|
}
|
|
7692
7814
|
|
|
7693
|
-
|
|
7815
|
+
let orderedFilteredPoints;
|
|
7816
|
+
if (pointOrderIndices !== null) {
|
|
7817
|
+
// Maintain the custom point order within the filtered set
|
|
7818
|
+
orderedFilteredPoints = [];
|
|
7819
|
+
for (let i = 0; i < pointOrderIndices.length; i++) {
|
|
7820
|
+
if (filteredPointsSet.has(pointOrderIndices[i])) {
|
|
7821
|
+
orderedFilteredPoints.push(pointOrderIndices[i]);
|
|
7822
|
+
}
|
|
7823
|
+
}
|
|
7824
|
+
} else {
|
|
7825
|
+
orderedFilteredPoints = insertionSort([...filteredPoints]);
|
|
7826
|
+
}
|
|
7694
7827
|
|
|
7695
|
-
for (const pointIdx of
|
|
7828
|
+
for (const pointIdx of orderedFilteredPoints) {
|
|
7696
7829
|
filteredPointsBuffer.push.apply(
|
|
7697
7830
|
filteredPointsBuffer,
|
|
7698
7831
|
indexToStateTexCoord(pointIdx),
|
|
@@ -8079,7 +8212,7 @@ const createScatterplot = (
|
|
|
8079
8212
|
continue;
|
|
8080
8213
|
}
|
|
8081
8214
|
|
|
8082
|
-
if (
|
|
8215
|
+
if (isPolygonAnnotation(annotation)) {
|
|
8083
8216
|
newPoints.push(annotation.vertices.flatMap(identity));
|
|
8084
8217
|
addColorAndWidth(annotation);
|
|
8085
8218
|
}
|
|
@@ -8120,7 +8253,6 @@ const createScatterplot = (
|
|
|
8120
8253
|
* @param {number[]} pointIdxs - A list of point indices
|
|
8121
8254
|
* @returns {import('./types').Rect} The bounding box
|
|
8122
8255
|
*/
|
|
8123
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
8124
8256
|
const getBBoxOfPoints = (pointIdxs) => {
|
|
8125
8257
|
let xMin = Number.POSITIVE_INFINITY;
|
|
8126
8258
|
let xMax = Number.NEGATIVE_INFINITY;
|
|
@@ -8156,7 +8288,6 @@ const createScatterplot = (
|
|
|
8156
8288
|
// Vertical field of view
|
|
8157
8289
|
// The Arc Tangent is based on the original camera position. Otherwise
|
|
8158
8290
|
// we would have to do `Math.atan(1 / camera.view[5])`
|
|
8159
|
-
// biome-ignore lint/style/useNamingConvention: FOV stands for field of view
|
|
8160
8291
|
const vFOV = 2 * Math.atan(1);
|
|
8161
8292
|
|
|
8162
8293
|
const aspectRatio = viewAspectRatio / dataAspectRatio;
|
|
@@ -8556,7 +8687,6 @@ const createScatterplot = (
|
|
|
8556
8687
|
reticleVLine.setStyle({ color: reticleColor });
|
|
8557
8688
|
};
|
|
8558
8689
|
|
|
8559
|
-
// biome-ignore lint/style/useNamingConvention: XScale are two words
|
|
8560
8690
|
const setXScale = (newXScale) => {
|
|
8561
8691
|
if (!newXScale) {
|
|
8562
8692
|
return;
|
|
@@ -8569,7 +8699,6 @@ const createScatterplot = (
|
|
|
8569
8699
|
updateScales();
|
|
8570
8700
|
};
|
|
8571
8701
|
|
|
8572
|
-
// biome-ignore lint/style/useNamingConvention: YScale are two words
|
|
8573
8702
|
const setYScale = (newYScale) => {
|
|
8574
8703
|
if (!newYScale) {
|
|
8575
8704
|
return;
|
|
@@ -8743,7 +8872,6 @@ const createScatterplot = (
|
|
|
8743
8872
|
annotationLineWidth = +newAnnotationLineWidth;
|
|
8744
8873
|
};
|
|
8745
8874
|
|
|
8746
|
-
// biome-ignore lint/style/useNamingConvention: HVLine stands for horizontal vertical line
|
|
8747
8875
|
const setAnnotationHVLineLimit = (newAnnotationHVLineLimit) => {
|
|
8748
8876
|
annotationHVLineLimit = +newAnnotationHVLineLimit;
|
|
8749
8877
|
};
|
|
@@ -8820,6 +8948,10 @@ const createScatterplot = (
|
|
|
8820
8948
|
return sizeBy;
|
|
8821
8949
|
}
|
|
8822
8950
|
|
|
8951
|
+
if (property === 'pointOrder') {
|
|
8952
|
+
return pointOrder !== null ? [...pointOrder] : null;
|
|
8953
|
+
}
|
|
8954
|
+
|
|
8823
8955
|
if (property === 'deselectOnDblClick') {
|
|
8824
8956
|
return deselectOnDblClick;
|
|
8825
8957
|
}
|
|
@@ -9206,6 +9338,10 @@ const createScatterplot = (
|
|
|
9206
9338
|
setSizeBy(properties.sizeBy);
|
|
9207
9339
|
}
|
|
9208
9340
|
|
|
9341
|
+
if (properties.pointOrder !== undefined) {
|
|
9342
|
+
setPointOrder(properties.pointOrder);
|
|
9343
|
+
}
|
|
9344
|
+
|
|
9209
9345
|
if (properties.opacity !== undefined) {
|
|
9210
9346
|
setOpacity(properties.opacity);
|
|
9211
9347
|
}
|
|
@@ -9903,6 +10039,9 @@ const createScatterplot = (
|
|
|
9903
10039
|
init();
|
|
9904
10040
|
|
|
9905
10041
|
return {
|
|
10042
|
+
attachSpatialIndex: (buffer) => {
|
|
10043
|
+
spatialIndex = kdbushFrom(buffer);
|
|
10044
|
+
},
|
|
9906
10045
|
/**
|
|
9907
10046
|
* Get whether the browser supports all necessary WebGL features
|
|
9908
10047
|
* @return {boolean} If `true` the browser supports all necessary WebGL features
|
|
@@ -9926,6 +10065,7 @@ const createScatterplot = (
|
|
|
9926
10065
|
get,
|
|
9927
10066
|
getScreenPosition,
|
|
9928
10067
|
hover,
|
|
10068
|
+
lassoSelect,
|
|
9929
10069
|
redraw,
|
|
9930
10070
|
refresh: renderer.refresh,
|
|
9931
10071
|
reset: withDraw(reset),
|