@hema-to/regl-scatterplot 1.14.1-hemato.3 → 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 +1782 -1624
- package/dist/regl-scatterplot.js +1745 -1743
- package/dist/regl-scatterplot.min.js +3 -8
- package/dist/types.d.ts +5 -6
- package/package.json +27 -27
- package/src/constants.js +1 -0
- package/src/index.js +216 -42
- 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 +0 -2
- package/src/spline-curve-worker.js +0 -3
- package/src/types.d.ts +5 -6
- 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};` +
|
|
@@ -2840,232 +2993,58 @@ const createWorker = (fn) => {
|
|
|
2840
2993
|
// Clean up URL
|
|
2841
2994
|
URL.revokeObjectURL(workerUrl);
|
|
2842
2995
|
|
|
2843
|
-
return worker;
|
|
2844
|
-
};
|
|
2845
|
-
|
|
2846
|
-
/**
|
|
2847
|
-
* Create KDBush from an either point data or an existing spatial index
|
|
2848
|
-
* @param {import('./types').Points | ArrayBuffer} pointsOrIndex - Points or KDBush index
|
|
2849
|
-
* @param {Partial<import('./types').CreateKDBushOptions>} options - Options for configuring the index and its creation
|
|
2850
|
-
* @return {Promise<KDBush>} KDBush instance
|
|
2851
|
-
*/
|
|
2852
|
-
const createKdbush = (
|
|
2853
|
-
pointsOrIndex,
|
|
2854
|
-
options = { nodeSize: 16, useWorker: undefined },
|
|
2855
|
-
) =>
|
|
2856
|
-
new Promise((resolve, reject) => {
|
|
2857
|
-
if (pointsOrIndex instanceof ArrayBuffer) {
|
|
2858
|
-
resolve(KDBush$1.from(pointsOrIndex));
|
|
2859
|
-
} else if (
|
|
2860
|
-
(pointsOrIndex.length < WORKER_THRESHOLD ||
|
|
2861
|
-
options.useWorker === false) &&
|
|
2862
|
-
options.useWorker !== true
|
|
2863
|
-
) {
|
|
2864
|
-
const index = new KDBush$1(pointsOrIndex.length, options.nodeSize);
|
|
2865
|
-
for (const pointOrIndex of pointsOrIndex) {
|
|
2866
|
-
index.add(pointOrIndex[0], pointOrIndex[1]);
|
|
2867
|
-
}
|
|
2868
|
-
index.finish();
|
|
2869
|
-
resolve(index);
|
|
2870
|
-
} else {
|
|
2871
|
-
const worker = createWorker(workerFn);
|
|
2872
|
-
|
|
2873
|
-
worker.onmessage = (e) => {
|
|
2874
|
-
if (e.data.error) {
|
|
2875
|
-
reject(e.data.error);
|
|
2876
|
-
} else {
|
|
2877
|
-
resolve(KDBush$1.from(e.data));
|
|
2878
|
-
}
|
|
2879
|
-
worker.terminate();
|
|
2880
|
-
};
|
|
2881
|
-
|
|
2882
|
-
worker.postMessage({ points: pointsOrIndex, nodeSize: options.nodeSize });
|
|
2883
|
-
}
|
|
2884
|
-
});
|
|
2885
|
-
|
|
2886
|
-
const DEFAULT_LASSO_START_INITIATOR_SHOW = true;
|
|
2887
|
-
const DEFAULT_LASSO_MIN_DELAY$1 = 8;
|
|
2888
|
-
const DEFAULT_LASSO_MIN_DIST$1 = 2;
|
|
2889
|
-
const DEFAULT_LASSO_TYPE$1 = 'freeform';
|
|
2890
|
-
const DEFAULT_BRUSH_SIZE = 24;
|
|
2891
|
-
const LASSO_SHOW_START_INITIATOR_TIME = 2500;
|
|
2892
|
-
const LASSO_HIDE_START_INITIATOR_TIME = 250;
|
|
2893
|
-
|
|
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;
|
|
2996
|
+
return worker;
|
|
2997
|
+
};
|
|
3023
2998
|
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
2999
|
+
/**
|
|
3000
|
+
* Create KDBush from an either point data or an existing spatial index
|
|
3001
|
+
* @param {import('./types').Points | ArrayBuffer} pointsOrIndex - Points or KDBush index
|
|
3002
|
+
* @param {Partial<import('./types').CreateKDBushOptions>} options - Options for configuring the index and its creation
|
|
3003
|
+
* @return {Promise<KDBush>} KDBush instance
|
|
3004
|
+
*/
|
|
3005
|
+
const createKdbush = (
|
|
3006
|
+
pointsOrIndex,
|
|
3007
|
+
options = { nodeSize: 16, useWorker: undefined },
|
|
3008
|
+
) =>
|
|
3009
|
+
new Promise((resolve, reject) => {
|
|
3010
|
+
if (pointsOrIndex instanceof ArrayBuffer) {
|
|
3011
|
+
resolve(KDBush$1.from(pointsOrIndex));
|
|
3012
|
+
} else if (
|
|
3013
|
+
(pointsOrIndex.length < WORKER_THRESHOLD ||
|
|
3014
|
+
options.useWorker === false) &&
|
|
3015
|
+
options.useWorker !== true
|
|
3016
|
+
) {
|
|
3017
|
+
const index = new KDBush$1(pointsOrIndex.length, options.nodeSize);
|
|
3018
|
+
for (const pointOrIndex of pointsOrIndex) {
|
|
3019
|
+
index.add(pointOrIndex[0], pointOrIndex[1]);
|
|
3020
|
+
}
|
|
3021
|
+
index.finish();
|
|
3022
|
+
resolve(index);
|
|
3023
|
+
} else {
|
|
3024
|
+
const worker = createWorker(workerFn);
|
|
3035
3025
|
|
|
3036
|
-
|
|
3037
|
-
|
|
3026
|
+
worker.onmessage = (e) => {
|
|
3027
|
+
if (e.data.error) {
|
|
3028
|
+
reject(e.data.error);
|
|
3029
|
+
} else {
|
|
3030
|
+
resolve(KDBush$1.from(e.data));
|
|
3031
|
+
}
|
|
3032
|
+
worker.terminate();
|
|
3033
|
+
};
|
|
3038
3034
|
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
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
|
-
);
|
|
3035
|
+
worker.postMessage({ points: pointsOrIndex, nodeSize: options.nodeSize });
|
|
3036
|
+
}
|
|
3037
|
+
});
|
|
3063
3038
|
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
const
|
|
3067
|
-
const
|
|
3068
|
-
|
|
3039
|
+
const kdbushFrom = (buffer) => KDBush$1.from(buffer);
|
|
3040
|
+
|
|
3041
|
+
const DEFAULT_LASSO_START_INITIATOR_SHOW = true;
|
|
3042
|
+
const DEFAULT_LASSO_MIN_DELAY = 8;
|
|
3043
|
+
const DEFAULT_LASSO_MIN_DIST = 2;
|
|
3044
|
+
const DEFAULT_LASSO_TYPE = 'freeform';
|
|
3045
|
+
const DEFAULT_BRUSH_SIZE = 24;
|
|
3046
|
+
const LASSO_SHOW_START_INITIATOR_TIME = 2500;
|
|
3047
|
+
const LASSO_HIDE_START_INITIATOR_TIME = 250;
|
|
3069
3048
|
|
|
3070
3049
|
const getInTime = (p, time, extraTime) => (1 - p) * time + extraTime;
|
|
3071
3050
|
|
|
@@ -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
|
) => {
|
|
@@ -4098,1086 +4077,1093 @@ const createLasso = (
|
|
|
4098
4077
|
newInitiatorParentElement !== null &&
|
|
4099
4078
|
newInitiatorParentElement !== initiatorParentElement
|
|
4100
4079
|
) {
|
|
4101
|
-
initiatorParentElement.removeChild(initiator);
|
|
4102
|
-
newInitiatorParentElement.appendChild(initiator);
|
|
4103
|
-
initiatorParentElement = newInitiatorParentElement;
|
|
4104
|
-
}
|
|
4105
|
-
|
|
4106
|
-
if (
|
|
4107
|
-
newLongPressIndicatorParentElement !== null &&
|
|
4108
|
-
newLongPressIndicatorParentElement !== longPressIndicatorParentElement
|
|
4109
|
-
) {
|
|
4110
|
-
longPressIndicatorParentElement.removeChild(longPress);
|
|
4111
|
-
newLongPressIndicatorParentElement.appendChild(longPress);
|
|
4112
|
-
longPressIndicatorParentElement = newLongPressIndicatorParentElement;
|
|
4113
|
-
}
|
|
4114
|
-
|
|
4115
|
-
if (enableInitiator) {
|
|
4116
|
-
initiator.addEventListener('click', initiatorClickHandler);
|
|
4117
|
-
initiator.addEventListener('mousedown', initiatorMouseDownHandler);
|
|
4118
|
-
initiator.addEventListener('mouseleave', initiatorMouseLeaveHandler);
|
|
4119
|
-
} else {
|
|
4120
|
-
initiator.removeEventListener('mousedown', initiatorMouseDownHandler);
|
|
4121
|
-
initiator.removeEventListener('mouseleave', initiatorMouseLeaveHandler);
|
|
4122
|
-
}
|
|
4123
|
-
|
|
4124
|
-
if (newType !== null) {
|
|
4125
|
-
setExtendLasso(newType);
|
|
4126
|
-
}
|
|
4127
|
-
};
|
|
4128
|
-
|
|
4129
|
-
const destroy = () => {
|
|
4130
|
-
initiatorParentElement.removeChild(initiator);
|
|
4131
|
-
longPressIndicatorParentElement.removeChild(longPress);
|
|
4132
|
-
window.removeEventListener('mouseup', mouseUpHandler);
|
|
4133
|
-
initiator.removeEventListener('click', initiatorClickHandler);
|
|
4134
|
-
initiator.removeEventListener('mousedown', initiatorMouseDownHandler);
|
|
4135
|
-
initiator.removeEventListener('mouseleave', initiatorMouseLeaveHandler);
|
|
4136
|
-
};
|
|
4137
|
-
|
|
4138
|
-
const withPublicMethods = () => (self) =>
|
|
4139
|
-
assign(self, {
|
|
4140
|
-
clear,
|
|
4141
|
-
destroy,
|
|
4142
|
-
end,
|
|
4143
|
-
extend: extendPublic,
|
|
4144
|
-
get,
|
|
4145
|
-
set,
|
|
4146
|
-
showInitiator,
|
|
4147
|
-
hideInitiator,
|
|
4148
|
-
showLongPressIndicator,
|
|
4149
|
-
hideLongPressIndicator,
|
|
4150
|
-
});
|
|
4151
|
-
|
|
4152
|
-
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
|
-
};
|
|
4196
|
-
|
|
4197
|
-
/**
|
|
4198
|
-
* Create a new Regl instance with `GL_EXTENSIONS` enables
|
|
4199
|
-
* @param {HTMLCanvasElement} canvas Canvas element to be rendered on
|
|
4200
|
-
* @return {import('regl').Regl} New Regl instance
|
|
4201
|
-
*/
|
|
4202
|
-
const createRegl = (canvas) => {
|
|
4203
|
-
const gl = canvas.getContext('webgl', {
|
|
4204
|
-
antialias: true,
|
|
4205
|
-
preserveDrawingBuffer: true,
|
|
4206
|
-
});
|
|
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
|
-
|
|
4222
|
-
return createOriginalRegl({ gl, extensions });
|
|
4223
|
-
};
|
|
4224
|
-
|
|
4225
|
-
/**
|
|
4226
|
-
* L2 distance between a pair of 2D points
|
|
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);
|
|
4235
|
-
|
|
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;
|
|
4248
|
-
|
|
4249
|
-
for (let i = 0; i < positions2d.length; i += 2) {
|
|
4250
|
-
xMin = positions2d[i] < xMin ? positions2d[i] : xMin;
|
|
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
|
-
}
|
|
4255
|
-
|
|
4256
|
-
return [xMin, yMin, xMax, yMax];
|
|
4257
|
-
};
|
|
4258
|
-
|
|
4259
|
-
/**
|
|
4260
|
-
* Test whether a bounding box is actually specifying an area
|
|
4261
|
-
* @param {array} bBox The bounding box to be checked
|
|
4262
|
-
* @return {array} `true` if the bounding box is valid
|
|
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;
|
|
4272
|
-
|
|
4273
|
-
const REGEX_HEX_TO_RGB = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
4080
|
+
initiatorParentElement.removeChild(initiator);
|
|
4081
|
+
newInitiatorParentElement.appendChild(initiator);
|
|
4082
|
+
initiatorParentElement = newInitiatorParentElement;
|
|
4083
|
+
}
|
|
4274
4084
|
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
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);
|
|
4085
|
+
if (
|
|
4086
|
+
newLongPressIndicatorParentElement !== null &&
|
|
4087
|
+
newLongPressIndicatorParentElement !== longPressIndicatorParentElement
|
|
4088
|
+
) {
|
|
4089
|
+
longPressIndicatorParentElement.removeChild(longPress);
|
|
4090
|
+
newLongPressIndicatorParentElement.appendChild(longPress);
|
|
4091
|
+
longPressIndicatorParentElement = newLongPressIndicatorParentElement;
|
|
4092
|
+
}
|
|
4288
4093
|
|
|
4289
|
-
|
|
4290
|
-
|
|
4094
|
+
if (enableInitiator) {
|
|
4095
|
+
initiator.addEventListener('click', initiatorClickHandler);
|
|
4096
|
+
initiator.addEventListener('mousedown', initiatorMouseDownHandler);
|
|
4097
|
+
initiator.addEventListener('mouseleave', initiatorMouseLeaveHandler);
|
|
4098
|
+
} else {
|
|
4099
|
+
initiator.removeEventListener('mousedown', initiatorMouseDownHandler);
|
|
4100
|
+
initiator.removeEventListener('mouseleave', initiatorMouseLeaveHandler);
|
|
4101
|
+
}
|
|
4291
4102
|
|
|
4292
|
-
|
|
4103
|
+
if (newType !== null) {
|
|
4104
|
+
setExtendLasso(newType);
|
|
4105
|
+
}
|
|
4106
|
+
};
|
|
4293
4107
|
|
|
4294
|
-
const
|
|
4108
|
+
const destroy = () => {
|
|
4109
|
+
initiatorParentElement.removeChild(initiator);
|
|
4110
|
+
longPressIndicatorParentElement.removeChild(longPress);
|
|
4111
|
+
window.removeEventListener('mouseup', mouseUpHandler);
|
|
4112
|
+
initiator.removeEventListener('click', initiatorClickHandler);
|
|
4113
|
+
initiator.removeEventListener('mousedown', initiatorMouseDownHandler);
|
|
4114
|
+
initiator.removeEventListener('mouseleave', initiatorMouseLeaveHandler);
|
|
4115
|
+
};
|
|
4295
4116
|
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4117
|
+
const withPublicMethods = () => (self) =>
|
|
4118
|
+
assign(self, {
|
|
4119
|
+
clear,
|
|
4120
|
+
destroy,
|
|
4121
|
+
end,
|
|
4122
|
+
extend: extendPublic,
|
|
4123
|
+
get,
|
|
4124
|
+
set,
|
|
4125
|
+
showInitiator,
|
|
4126
|
+
hideInitiator,
|
|
4127
|
+
showLongPressIndicator,
|
|
4128
|
+
hideLongPressIndicator,
|
|
4129
|
+
});
|
|
4304
4130
|
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
* @param {string} src Remote image source, i.e., a URL
|
|
4308
|
-
* @param {boolean} isCrossOrigin If `true` allow loading image from a source of another origin.
|
|
4309
|
-
* @return {Promise<HTMLImageElement>} Promise resolving to the image once its loaded
|
|
4310
|
-
*/
|
|
4311
|
-
const loadImage = (
|
|
4312
|
-
src,
|
|
4313
|
-
isCrossOrigin = false,
|
|
4314
|
-
timeout = DEFAULT_IMAGE_LOAD_TIMEOUT,
|
|
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
|
-
});
|
|
4131
|
+
initiatorParentElement.appendChild(initiator);
|
|
4132
|
+
longPressIndicatorParentElement.appendChild(longPress);
|
|
4331
4133
|
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
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
|
-
});
|
|
4134
|
+
set({
|
|
4135
|
+
onDraw,
|
|
4136
|
+
onStart,
|
|
4137
|
+
onEnd,
|
|
4138
|
+
enableInitiator,
|
|
4139
|
+
initiatorParentElement,
|
|
4140
|
+
type,
|
|
4141
|
+
brushSize,
|
|
4357
4142
|
});
|
|
4358
4143
|
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
const hexToRgba = (hex, isNormalize = false) => [
|
|
4367
|
-
...hexToRgb(hex, isNormalize),
|
|
4368
|
-
255 ** !isNormalize,
|
|
4369
|
-
];
|
|
4370
|
-
|
|
4371
|
-
const REGEX_IS_HEX = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i;
|
|
4144
|
+
return pipe(
|
|
4145
|
+
withStaticProperty('initiator', initiator),
|
|
4146
|
+
withStaticProperty('longPressIndicator', longPress),
|
|
4147
|
+
withPublicMethods(),
|
|
4148
|
+
withConstructor(createLasso),
|
|
4149
|
+
)({});
|
|
4150
|
+
};
|
|
4372
4151
|
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
* @param {string} hex HEX-encoded color string.
|
|
4376
|
-
* @return {boolean} If `true` the string is a valid HEX color encoding.
|
|
4377
|
-
*/
|
|
4378
|
-
const isHex = (hex) => REGEX_IS_HEX.test(hex);
|
|
4152
|
+
const FRAGMENT_SHADER$1 = `
|
|
4153
|
+
precision highp float;
|
|
4379
4154
|
|
|
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;
|
|
4155
|
+
uniform float antiAliasing;
|
|
4386
4156
|
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
* @param {array} a Array to be tested
|
|
4390
|
-
* @return {boolean} If `true` the array contains only numbers in `[0,1]`.
|
|
4391
|
-
*/
|
|
4392
|
-
const isNormFloatArray = (a) => Array.isArray(a) && a.every(isNormFloat);
|
|
4157
|
+
varying vec4 color;
|
|
4158
|
+
varying float finalPointSize;
|
|
4393
4159
|
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
* @param {number} x1 X-coordinate of first point
|
|
4397
|
-
* @param {number} y1 Y-coordinate of first point
|
|
4398
|
-
* @param {number} x2 X-coordinate of second point
|
|
4399
|
-
* @param {number} y2 Y-coordinate of second point
|
|
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);
|
|
4160
|
+
float linearstep(float edge0, float edge1, float x) {
|
|
4161
|
+
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
4406
4162
|
}
|
|
4407
4163
|
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
* @param {Array} point Tuple of the form [x,y] to be tested
|
|
4413
|
-
* @return {boolean} True if point lies within the polygon
|
|
4414
|
-
*/
|
|
4415
|
-
const isPointInPolygon = (polygon, [px, py] = []) => {
|
|
4416
|
-
let winding = 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);
|
|
4417
4168
|
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
const x2 = polygon[j];
|
|
4422
|
-
const y2 = polygon[j + 1];
|
|
4169
|
+
gl_FragColor = vec4(color.rgb, alpha * color.a);
|
|
4170
|
+
}
|
|
4171
|
+
`;
|
|
4423
4172
|
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
const orientation = crossProduct(x1, y1, x2, y2, px, py);
|
|
4427
|
-
if (orientation > 0) {
|
|
4428
|
-
winding++;
|
|
4429
|
-
}
|
|
4430
|
-
}
|
|
4431
|
-
} else if (y2 <= py) {
|
|
4432
|
-
const orientation = crossProduct(x1, y1, x2, y2, px, py);
|
|
4433
|
-
if (orientation < 0) {
|
|
4434
|
-
winding--;
|
|
4435
|
-
}
|
|
4436
|
-
}
|
|
4173
|
+
const createVertexShader = (globalState) => `
|
|
4174
|
+
precision highp float;
|
|
4437
4175
|
|
|
4438
|
-
|
|
4439
|
-
|
|
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;
|
|
4440
4208
|
|
|
4441
|
-
|
|
4442
|
-
};
|
|
4209
|
+
attribute vec2 stateIndex;
|
|
4443
4210
|
|
|
4444
|
-
|
|
4445
|
-
|
|
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;
|
|
4211
|
+
varying vec4 color;
|
|
4212
|
+
varying float finalPointSize;
|
|
4450
4213
|
|
|
4451
|
-
|
|
4452
|
-
|
|
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;
|
|
4214
|
+
void main() {
|
|
4215
|
+
vec4 state = texture2D(stateTex, stateIndex);
|
|
4457
4216
|
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
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
|
+
}
|
|
4464
4227
|
|
|
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));
|
|
4473
4228
|
|
|
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));
|
|
4229
|
+
// Determine color index
|
|
4230
|
+
float colorIndexZ = isColoredByZ * floor(state.z * colorMultiplicator);
|
|
4231
|
+
float colorIndexW = isColoredByW * floor(state.w * colorMultiplicator);
|
|
4482
4232
|
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
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]));
|
|
4233
|
+
// Multiply by the number of color states per color
|
|
4234
|
+
// I.e., normal, active, hover, background, etc.
|
|
4235
|
+
float colorIndex = (colorIndexZ + colorIndexW) * numColorStates;
|
|
4492
4236
|
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
*/
|
|
4496
|
-
const isSameRgbas = (a, b) => {
|
|
4497
|
-
if (!(Array.isArray(a) && Array.isArray(b)) || a.length !== b.length) {
|
|
4498
|
-
return false;
|
|
4499
|
-
}
|
|
4237
|
+
// Half a "pixel" or "texel" in texture coordinates
|
|
4238
|
+
float colorLinearIndex = colorIndex + globalState;
|
|
4500
4239
|
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
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);
|
|
4504
4244
|
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4245
|
+
vec2 colorTexIndex = vec2(
|
|
4246
|
+
(colorLinearIndex / colorTexRes) - colorRowIndex + colorTexEps,
|
|
4247
|
+
colorRowIndex / colorTexRes + colorTexEps
|
|
4248
|
+
);
|
|
4509
4249
|
|
|
4510
|
-
|
|
4511
|
-
const [r2, g2, b2, a2] = b[i];
|
|
4512
|
-
return r1 === r2 && g1 === g2 && b1 === b2 && a1 === a2;
|
|
4513
|
-
});
|
|
4514
|
-
};
|
|
4250
|
+
color = texture2D(colorTex, colorTexIndex);
|
|
4515
4251
|
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
*
|
|
4519
|
-
|
|
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);
|
|
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;
|
|
4525
4256
|
|
|
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);
|
|
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;
|
|
4535
4263
|
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
* normalized to `[0,1]`.
|
|
4542
|
-
* @return {array} Quadruple defining an RGBA color.
|
|
4543
|
-
*/
|
|
4544
|
-
const toRgba = (color, shouldNormalize) => {
|
|
4545
|
-
if (isRgba(color)) {
|
|
4546
|
-
const isNormalized = isNormFloatArray(color);
|
|
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
|
-
}
|
|
4264
|
+
// Retrieve opacity
|
|
4265
|
+
${
|
|
4266
|
+
(() => {
|
|
4267
|
+
// Drawing the inner border of selected points
|
|
4268
|
+
if (globalState === 3) return '';
|
|
4558
4269
|
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
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;
|
|
4562
4276
|
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
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);
|
|
4285
|
+
}
|
|
4286
|
+
`;
|
|
4287
|
+
})()
|
|
4573
4288
|
}
|
|
4574
4289
|
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4290
|
+
color.a = min(pointOpacityMax, color.a) * pointOpacityScale;
|
|
4291
|
+
finalPointSize = (pointSize * pointScale) + pointSizeExtra;
|
|
4292
|
+
gl_PointSize = finalPointSize;
|
|
4293
|
+
}
|
|
4294
|
+
`;
|
|
4578
4295
|
|
|
4579
|
-
|
|
4580
|
-
console.warn(
|
|
4581
|
-
'Only HEX, RGB, and RGBA are handled by this function. Returning white instead.',
|
|
4582
|
-
);
|
|
4583
|
-
return shouldNormalize ? [1, 1, 1, 1] : [255, 255, 255, 255];
|
|
4584
|
-
};
|
|
4296
|
+
const FRAGMENT_SHADER = `precision highp float;
|
|
4585
4297
|
|
|
4586
|
-
|
|
4587
|
-
* Flip the key-value pairs of an object
|
|
4588
|
-
* @param {object} obj - Object to be flipped
|
|
4589
|
-
* @return {object} Flipped object
|
|
4590
|
-
*/
|
|
4591
|
-
const flipObj = (obj) =>
|
|
4592
|
-
Object.entries(obj).reduce((out, [key, value]) => {
|
|
4593
|
-
if (out[value]) {
|
|
4594
|
-
out[value] = [...out[value], key];
|
|
4595
|
-
} else {
|
|
4596
|
-
out[value] = key;
|
|
4597
|
-
}
|
|
4598
|
-
return out;
|
|
4599
|
-
}, {});
|
|
4298
|
+
varying vec4 color;
|
|
4600
4299
|
|
|
4601
|
-
|
|
4602
|
-
|
|
4300
|
+
void main() {
|
|
4301
|
+
gl_FragColor = color;
|
|
4302
|
+
}
|
|
4303
|
+
`;
|
|
4603
4304
|
|
|
4604
|
-
|
|
4605
|
-
* Clip a number between min and max
|
|
4606
|
-
* @param {number} value - The value to be clipped
|
|
4607
|
-
* @param {number} minValue - The minimum value
|
|
4608
|
-
* @param {number} maxValue - The maximum value
|
|
4609
|
-
* @return {number} The clipped value
|
|
4610
|
-
*/
|
|
4611
|
-
const clip = (value, minValue, maxValue) =>
|
|
4612
|
-
Math.min(maxValue, Math.max(minValue, value));
|
|
4305
|
+
const SHADER$1 = `precision highp float;
|
|
4613
4306
|
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
* @return {number[][]} Array-oriented points
|
|
4618
|
-
*/
|
|
4619
|
-
const toArrayOrientedPoints = (points) =>
|
|
4620
|
-
new Promise((resolve, reject) => {
|
|
4621
|
-
if (!points || Array.isArray(points)) {
|
|
4622
|
-
resolve(points);
|
|
4623
|
-
} else {
|
|
4624
|
-
const length =
|
|
4625
|
-
Array.isArray(points.x) || ArrayBuffer.isView(points.x)
|
|
4626
|
-
? points.x.length
|
|
4627
|
-
: 0;
|
|
4307
|
+
uniform sampler2D startStateTex;
|
|
4308
|
+
uniform sampler2D endStateTex;
|
|
4309
|
+
uniform float t;
|
|
4628
4310
|
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
(Array.isArray(points.line) || ArrayBuffer.isView(points.line)) &&
|
|
4637
|
-
((i) => points.line[i]);
|
|
4311
|
+
varying vec2 particleTextureIndex;
|
|
4312
|
+
|
|
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;
|
|
4638
4318
|
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
(Array.isArray(points.lineOrder) ||
|
|
4642
|
-
ArrayBuffer.isView(points.lineOrder)) &&
|
|
4643
|
-
((i) => points.lineOrder[i]);
|
|
4319
|
+
// The category cannot be interpolated
|
|
4320
|
+
float endCategory = texture2D(endStateTex, particleTextureIndex).z;
|
|
4644
4321
|
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
const z = components.find((c) => Z_NAMES.has(c));
|
|
4648
|
-
return (
|
|
4649
|
-
z &&
|
|
4650
|
-
(Array.isArray(points[z]) || ArrayBuffer.isView(points[z])) &&
|
|
4651
|
-
((i) => points[z][i])
|
|
4652
|
-
);
|
|
4653
|
-
})();
|
|
4654
|
-
const getW = (() => {
|
|
4655
|
-
const w = components.find((c) => W_NAMES.has(c));
|
|
4656
|
-
return (
|
|
4657
|
-
w &&
|
|
4658
|
-
(Array.isArray(points[w]) || ArrayBuffer.isView(points[w])) &&
|
|
4659
|
-
((i) => points[w][i])
|
|
4660
|
-
);
|
|
4661
|
-
})();
|
|
4322
|
+
gl_FragColor = vec4(curr.xy, endCategory, curr.z);
|
|
4323
|
+
}`;
|
|
4662
4324
|
|
|
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
|
-
});
|
|
4325
|
+
const SHADER = `precision highp float;
|
|
4702
4326
|
|
|
4703
|
-
|
|
4704
|
-
|
|
4327
|
+
attribute vec2 position;
|
|
4328
|
+
varying vec2 particleTextureIndex;
|
|
4705
4329
|
|
|
4706
|
-
|
|
4707
|
-
|
|
4330
|
+
void main() {
|
|
4331
|
+
// map normalized device coords to texture coords
|
|
4332
|
+
particleTextureIndex = 0.5 * (1.0 + position);
|
|
4708
4333
|
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
Number.isFinite(annotation.y) &&
|
|
4712
|
-
Number.isFinite(annotation.width) &&
|
|
4713
|
-
Number.isFinite(annotation.height);
|
|
4334
|
+
gl_Position = vec4(position, 0, 1);
|
|
4335
|
+
}`;
|
|
4714
4336
|
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4337
|
+
/**
|
|
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
|
|
4342
|
+
*/
|
|
4343
|
+
const checkReglExtensions = (regl, silent) => {
|
|
4344
|
+
if (!regl) {
|
|
4345
|
+
return false;
|
|
4346
|
+
}
|
|
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);
|
|
4359
|
+
};
|
|
4720
4360
|
|
|
4721
|
-
|
|
4722
|
-
|
|
4361
|
+
/**
|
|
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
|
|
4365
|
+
*/
|
|
4366
|
+
const createRegl = (canvas) => {
|
|
4367
|
+
const gl = canvas.getContext('webgl', {
|
|
4368
|
+
antialias: true,
|
|
4369
|
+
preserveDrawingBuffer: true,
|
|
4370
|
+
});
|
|
4371
|
+
const extensions = [];
|
|
4723
4372
|
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
for (
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
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);
|
|
4378
|
+
} else {
|
|
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
|
+
);
|
|
4734
4383
|
}
|
|
4735
|
-
array[j + 1] = current;
|
|
4736
4384
|
}
|
|
4737
|
-
|
|
4385
|
+
|
|
4386
|
+
return createOriginalRegl({ gl, extensions });
|
|
4738
4387
|
};
|
|
4739
4388
|
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4389
|
+
/**
|
|
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
|
|
4396
|
+
*/
|
|
4397
|
+
const dist = (x1, y1, x2, y2) =>
|
|
4398
|
+
Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
|
|
4748
4399
|
|
|
4749
|
-
|
|
4400
|
+
/**
|
|
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
|
|
4405
|
+
*/
|
|
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;
|
|
4750
4411
|
|
|
4751
|
-
|
|
4752
|
-
|
|
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;
|
|
4753
4417
|
}
|
|
4754
4418
|
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
const fboRes = [canvas.width, canvas.height];
|
|
4758
|
-
const fbo = regl.framebuffer({
|
|
4759
|
-
width: fboRes[0],
|
|
4760
|
-
height: fboRes[1],
|
|
4761
|
-
colorFormat: 'rgba',
|
|
4762
|
-
colorType: 'float',
|
|
4763
|
-
});
|
|
4419
|
+
return [xMin, yMin, xMax, yMax];
|
|
4420
|
+
};
|
|
4764
4421
|
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
frag: `
|
|
4778
|
-
precision highp float;
|
|
4779
|
-
uniform vec2 srcRes;
|
|
4780
|
-
uniform sampler2D src;
|
|
4781
|
-
uniform float gamma;
|
|
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;
|
|
4782
4434
|
|
|
4783
|
-
|
|
4784
|
-
return pow(clamp(rgb, vec3(0), vec3(1)), vec3(1.0 / gamma));
|
|
4785
|
-
}
|
|
4435
|
+
const REGEX_HEX_TO_RGB = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
4786
4436
|
|
|
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
|
-
});
|
|
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);
|
|
4813
4450
|
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
*/
|
|
4817
|
-
const copyTo = (targetCanvas) => {
|
|
4818
|
-
const ctx = targetCanvas.getContext('2d');
|
|
4819
|
-
ctx.clearRect(0, 0, targetCanvas.width, targetCanvas.height);
|
|
4820
|
-
ctx.drawImage(
|
|
4821
|
-
canvas,
|
|
4822
|
-
(canvas.width - targetCanvas.width) / 2,
|
|
4823
|
-
(canvas.height - targetCanvas.height) / 2,
|
|
4824
|
-
targetCanvas.width,
|
|
4825
|
-
targetCanvas.height,
|
|
4826
|
-
0,
|
|
4827
|
-
0,
|
|
4828
|
-
targetCanvas.width,
|
|
4829
|
-
targetCanvas.height,
|
|
4830
|
-
);
|
|
4831
|
-
};
|
|
4451
|
+
const isConditionalArray = (a, condition, { minLength = 0 } = {}) =>
|
|
4452
|
+
Array.isArray(a) && a.length >= minLength && a.every(condition);
|
|
4832
4453
|
|
|
4833
|
-
|
|
4834
|
-
* The render function
|
|
4835
|
-
*/
|
|
4836
|
-
const render = (
|
|
4837
|
-
/** @type {(): void} */ draw,
|
|
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
|
-
};
|
|
4454
|
+
const isPositiveNumber = (x) => !Number.isNaN(+x) && +x >= 0;
|
|
4850
4455
|
|
|
4851
|
-
|
|
4852
|
-
* Update Regl's viewport, drawingBufferWidth, and drawingBufferHeight
|
|
4853
|
-
*
|
|
4854
|
-
* @description Call this method after the viewport has changed, e.g., width
|
|
4855
|
-
* or height have been altered
|
|
4856
|
-
*/
|
|
4857
|
-
const refresh = () => {
|
|
4858
|
-
regl.poll();
|
|
4859
|
-
};
|
|
4456
|
+
const isStrictlyPositiveNumber = (x) => !Number.isNaN(+x) && +x > 0;
|
|
4860
4457
|
|
|
4861
|
-
|
|
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;
|
|
4862
4466
|
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
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';
|
|
4482
|
+
}
|
|
4483
|
+
image.src = src;
|
|
4484
|
+
image.onload = () => {
|
|
4485
|
+
resolve(image);
|
|
4871
4486
|
};
|
|
4872
|
-
|
|
4487
|
+
const rejectPromise = () => {
|
|
4488
|
+
reject(new Error(IMAGE_LOAD_ERROR));
|
|
4489
|
+
};
|
|
4490
|
+
image.onerror = rejectPromise;
|
|
4491
|
+
setTimeout(rejectPromise, timeout);
|
|
4492
|
+
});
|
|
4873
4493
|
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
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
|
+
});
|
|
4881
4519
|
});
|
|
4882
4520
|
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
};
|
|
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
|
+
];
|
|
4895
4532
|
|
|
4896
|
-
|
|
4897
|
-
resize();
|
|
4898
|
-
};
|
|
4533
|
+
const REGEX_IS_HEX = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i;
|
|
4899
4534
|
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
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);
|
|
4905
4541
|
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
window.removeEventListener('orientationchange', resizeHandler);
|
|
4913
|
-
frame.cancel();
|
|
4914
|
-
canvas = undefined;
|
|
4915
|
-
regl.destroy();
|
|
4916
|
-
regl = undefined;
|
|
4917
|
-
};
|
|
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;
|
|
4918
4548
|
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
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);
|
|
4555
|
+
|
|
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
|
+
}
|
|
4569
|
+
|
|
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;
|
|
4579
|
+
|
|
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];
|
|
4585
|
+
|
|
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
|
+
}
|
|
4599
|
+
|
|
4600
|
+
j = i;
|
|
4601
|
+
}
|
|
4602
|
+
|
|
4603
|
+
return winding !== 0;
|
|
4968
4604
|
};
|
|
4969
4605
|
|
|
4970
|
-
|
|
4971
|
-
|
|
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;
|
|
4972
4612
|
|
|
4973
|
-
|
|
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;
|
|
4974
4619
|
|
|
4975
|
-
|
|
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);
|
|
4976
4626
|
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
}
|
|
4980
|
-
|
|
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));
|
|
4981
4635
|
|
|
4982
|
-
|
|
4983
|
-
|
|
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));
|
|
4984
4644
|
|
|
4985
|
-
|
|
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]));
|
|
4986
4654
|
|
|
4987
|
-
|
|
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
|
+
}
|
|
4988
4662
|
|
|
4989
|
-
|
|
4663
|
+
if (a.length === 0) {
|
|
4664
|
+
return true;
|
|
4665
|
+
}
|
|
4990
4666
|
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
}
|
|
4995
|
-
`;
|
|
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
|
+
}
|
|
4996
4671
|
|
|
4997
|
-
|
|
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
|
+
};
|
|
4998
4677
|
|
|
4999
|
-
|
|
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);
|
|
5000
4687
|
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
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);
|
|
5005
4697
|
|
|
5006
|
-
|
|
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
|
+
}
|
|
5007
4720
|
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
4721
|
+
if (isRgb(color)) {
|
|
4722
|
+
const base = 255 ** !shouldNormalize;
|
|
4723
|
+
const isNormalized = isNormFloatArray(color);
|
|
5011
4724
|
|
|
5012
|
-
|
|
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
|
+
}
|
|
5013
4736
|
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
vec3 end = texture2D(endStateTex, particleTextureIndex).xyw;
|
|
5018
|
-
vec3 curr = start * (1.0 - t) + end * t;
|
|
4737
|
+
if (isHex(color)) {
|
|
4738
|
+
return hexToRgba(color, shouldNormalize);
|
|
4739
|
+
}
|
|
5019
4740
|
|
|
5020
|
-
//
|
|
5021
|
-
|
|
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
|
+
};
|
|
5022
4747
|
|
|
5023
|
-
|
|
5024
|
-
|
|
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
|
+
}, {});
|
|
5025
4762
|
|
|
5026
|
-
const
|
|
4763
|
+
const rgbBrightness = (rgb) =>
|
|
4764
|
+
0.21 * rgb[0] + 0.72 * rgb[1] + 0.07 * rgb[2];
|
|
4765
|
+
|
|
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));
|
|
4775
|
+
|
|
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;
|
|
4790
|
+
|
|
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]);
|
|
4800
|
+
|
|
4801
|
+
const getLO =
|
|
4802
|
+
(Array.isArray(points.lineOrder) ||
|
|
4803
|
+
ArrayBuffer.isView(points.lineOrder)) &&
|
|
4804
|
+
((i) => points.lineOrder[i]);
|
|
5027
4805
|
|
|
5028
|
-
|
|
5029
|
-
|
|
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
|
+
})();
|
|
5030
4823
|
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
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
|
+
});
|
|
5034
4863
|
|
|
5035
|
-
|
|
5036
|
-
|
|
4864
|
+
const isHorizontalLine = (annotation) =>
|
|
4865
|
+
Number.isFinite(annotation.y) && !('x' in annotation);
|
|
5037
4866
|
|
|
5038
|
-
const
|
|
5039
|
-
|
|
4867
|
+
const isVerticalLine = (annotation) =>
|
|
4868
|
+
Number.isFinite(annotation.x) && !('y' in annotation);
|
|
5040
4869
|
|
|
5041
|
-
|
|
4870
|
+
const isDomRect = (annotation) =>
|
|
4871
|
+
Number.isFinite(annotation.x) &&
|
|
4872
|
+
Number.isFinite(annotation.y) &&
|
|
4873
|
+
Number.isFinite(annotation.width) &&
|
|
4874
|
+
Number.isFinite(annotation.height);
|
|
5042
4875
|
|
|
5043
|
-
|
|
5044
|
-
|
|
4876
|
+
const isRect = (annotation) =>
|
|
4877
|
+
Number.isFinite(annotation.x1) &&
|
|
4878
|
+
Number.isFinite(annotation.y1) &&
|
|
4879
|
+
Number.isFinite(annotation.x2) &&
|
|
4880
|
+
Number.isFinite(annotation.x2);
|
|
5045
4881
|
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
}
|
|
4882
|
+
const isPolygonAnnotation = (annotation) =>
|
|
4883
|
+
'vertices' in annotation && annotation.vertices.length > 1;
|
|
5049
4884
|
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
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
|
+
}
|
|
5054
4894
|
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
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
|
+
}
|
|
5058
4905
|
|
|
5059
|
-
|
|
5060
|
-
|
|
4906
|
+
return true;
|
|
4907
|
+
};
|
|
5061
4908
|
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
uniform float globalState;
|
|
5077
|
-
uniform float isColoredByZ;
|
|
5078
|
-
uniform float isColoredByW;
|
|
5079
|
-
uniform float isOpacityByZ;
|
|
5080
|
-
uniform float isOpacityByW;
|
|
5081
|
-
uniform float isOpacityByDensity;
|
|
5082
|
-
uniform float isSizedByZ;
|
|
5083
|
-
uniform float isSizedByW;
|
|
5084
|
-
uniform float isPixelAligned;
|
|
5085
|
-
uniform float colorMultiplicator;
|
|
5086
|
-
uniform float opacityMultiplicator;
|
|
5087
|
-
uniform float opacityDensity;
|
|
5088
|
-
uniform float sizeMultiplicator;
|
|
5089
|
-
uniform float numColorStates;
|
|
5090
|
-
uniform float pointScale;
|
|
5091
|
-
uniform float drawingBufferWidth;
|
|
5092
|
-
uniform float drawingBufferHeight;
|
|
5093
|
-
uniform mat4 modelViewProjection;
|
|
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
|
+
};
|
|
5094
4923
|
|
|
5095
|
-
|
|
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
|
+
};
|
|
5096
4939
|
|
|
5097
|
-
|
|
5098
|
-
|
|
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;
|
|
5099
4948
|
|
|
5100
|
-
|
|
5101
|
-
vec4 state = texture2D(stateTex, stateIndex);
|
|
4949
|
+
let isDestroyed = false;
|
|
5102
4950
|
|
|
5103
|
-
if (
|
|
5104
|
-
|
|
5105
|
-
} else {
|
|
5106
|
-
vec4 clipSpacePosition = modelViewProjection * vec4(state.x, state.y, 0.0, 1.0);
|
|
5107
|
-
vec2 ndcPosition = clipSpacePosition.xy / clipSpacePosition.w;
|
|
5108
|
-
vec2 pixelPos = 0.5 * (ndcPosition + 1.0) * vec2(drawingBufferWidth, drawingBufferHeight);
|
|
5109
|
-
pixelPos = floor(pixelPos + 0.5); // Snap to nearest pixel
|
|
5110
|
-
vec2 snappedPosition = (pixelPos / vec2(drawingBufferWidth, drawingBufferHeight)) * 2.0 - 1.0;
|
|
5111
|
-
gl_Position = vec4(snappedPosition, 0.0, 1.0);
|
|
4951
|
+
if (!regl) {
|
|
4952
|
+
regl = createRegl(canvas);
|
|
5112
4953
|
}
|
|
5113
4954
|
|
|
4955
|
+
const isSupportingAllGlExtensions = checkReglExtensions(regl);
|
|
5114
4956
|
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
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
|
+
});
|
|
5118
4964
|
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
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;
|
|
5122
4982
|
|
|
5123
|
-
|
|
5124
|
-
|
|
4983
|
+
vec3 approxLinearToSRGB (vec3 rgb, float gamma) {
|
|
4984
|
+
return pow(clamp(rgb, vec3(0), vec3(1)), vec3(1.0 / gamma));
|
|
4985
|
+
}
|
|
4986
|
+
|
|
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
|
+
});
|
|
5011
|
+
|
|
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
|
+
};
|
|
5125
5030
|
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
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
|
+
};
|
|
5130
5048
|
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
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
|
+
};
|
|
5135
5058
|
|
|
5136
|
-
|
|
5059
|
+
const drawFns = new Set();
|
|
5137
5060
|
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
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
|
+
};
|
|
5142
5071
|
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
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
|
+
});
|
|
5149
5080
|
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
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
|
+
};
|
|
5155
5093
|
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
float opacityIndexZ = isOpacityByZ * floor(state.z * opacityMultiplicator);
|
|
5160
|
-
float opacityIndexW = isOpacityByW * floor(state.w * opacityMultiplicator);
|
|
5161
|
-
float opacityIndex = opacityIndexZ + opacityIndexW;
|
|
5094
|
+
const resizeHandler = () => {
|
|
5095
|
+
resize();
|
|
5096
|
+
};
|
|
5162
5097
|
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
);
|
|
5168
|
-
color.a = texture2D(encodingTex, opacityTexIndex)[${1 + globalState}];
|
|
5169
|
-
} else {
|
|
5170
|
-
color.a = min(1.0, opacityDensity + globalState);
|
|
5171
|
-
}
|
|
5172
|
-
`;
|
|
5173
|
-
})()
|
|
5098
|
+
if (!options.canvas) {
|
|
5099
|
+
window.addEventListener('resize', resizeHandler);
|
|
5100
|
+
window.addEventListener('orientationchange', resizeHandler);
|
|
5101
|
+
resize();
|
|
5174
5102
|
}
|
|
5175
5103
|
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
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
|
+
};
|
|
5181
5167
|
|
|
5182
5168
|
/* eslint-env worker */
|
|
5183
5169
|
/* eslint no-restricted-globals: 1 */
|
|
@@ -5286,7 +5272,6 @@ const worker = function worker() {
|
|
|
5286
5272
|
* @param {[type]} simplified [description]
|
|
5287
5273
|
* @return {[type]} [description]
|
|
5288
5274
|
*/
|
|
5289
|
-
// biome-ignore lint/style/useNamingConvention: DP stands for Douglas Peucker
|
|
5290
5275
|
const simplifyDPStep = (points, first, last, tolerance, simplified) => {
|
|
5291
5276
|
let maxDist = tolerance;
|
|
5292
5277
|
let index;
|
|
@@ -5394,7 +5379,6 @@ const worker = function worker() {
|
|
|
5394
5379
|
const groupedPoints = {};
|
|
5395
5380
|
|
|
5396
5381
|
const isOrdered = !Number.isNaN(+points[0][5]);
|
|
5397
|
-
// biome-ignore lint/complexity/noForEach: somehow for .. of does not work in a worker
|
|
5398
5382
|
points.forEach((point) => {
|
|
5399
5383
|
const segId = point[4];
|
|
5400
5384
|
|
|
@@ -5410,7 +5394,6 @@ const worker = function worker() {
|
|
|
5410
5394
|
});
|
|
5411
5395
|
|
|
5412
5396
|
// The filtering ensures that non-existing array entries are removed
|
|
5413
|
-
// biome-ignore lint/complexity/noForEach: somehow for .. of does not work in a worker
|
|
5414
5397
|
Object.entries(groupedPoints).forEach((idPoints) => {
|
|
5415
5398
|
groupedPoints[idPoints[0]] = idPoints[1].filter((v) => v);
|
|
5416
5399
|
// Store the first point as the reference
|
|
@@ -5467,8 +5450,6 @@ const createSplineCurve = (
|
|
|
5467
5450
|
worker$1.postMessage({ points, options });
|
|
5468
5451
|
});
|
|
5469
5452
|
|
|
5470
|
-
var version = "1.14.1-hemato.3";
|
|
5471
|
-
|
|
5472
5453
|
const deprecations = {
|
|
5473
5454
|
showRecticle: {
|
|
5474
5455
|
replacement: 'showReticle',
|
|
@@ -5577,8 +5558,8 @@ const createScatterplot = (
|
|
|
5577
5558
|
deselectOnEscape = DEFAULT_DESELECT_ON_ESCAPE,
|
|
5578
5559
|
lassoColor = DEFAULT_LASSO_COLOR,
|
|
5579
5560
|
lassoLineWidth = DEFAULT_LASSO_LINE_WIDTH,
|
|
5580
|
-
lassoMinDelay = DEFAULT_LASSO_MIN_DELAY,
|
|
5581
|
-
lassoMinDist = DEFAULT_LASSO_MIN_DIST,
|
|
5561
|
+
lassoMinDelay = DEFAULT_LASSO_MIN_DELAY$1,
|
|
5562
|
+
lassoMinDist = DEFAULT_LASSO_MIN_DIST$1,
|
|
5582
5563
|
lassoMode = DEFAULT_LASSO_MODE,
|
|
5583
5564
|
lassoClearEvent = DEFAULT_LASSO_CLEAR_EVENT,
|
|
5584
5565
|
lassoInitiator = DEFAULT_LASSO_INITIATOR,
|
|
@@ -5589,7 +5570,7 @@ const createScatterplot = (
|
|
|
5589
5570
|
lassoLongPressAfterEffectTime = DEFAULT_LASSO_LONG_PRESS_AFTER_EFFECT_TIME,
|
|
5590
5571
|
lassoLongPressEffectDelay = DEFAULT_LASSO_LONG_PRESS_EFFECT_DELAY,
|
|
5591
5572
|
lassoLongPressRevertEffectTime = DEFAULT_LASSO_LONG_PRESS_REVERT_EFFECT_TIME,
|
|
5592
|
-
lassoType = DEFAULT_LASSO_TYPE,
|
|
5573
|
+
lassoType = DEFAULT_LASSO_TYPE$1,
|
|
5593
5574
|
lassoBrushSize = DEFAULT_LASSO_BRUSH_SIZE,
|
|
5594
5575
|
actionKeyMap = DEFAULT_ACTION_KEY_MAP,
|
|
5595
5576
|
mouseMode = DEFAULT_MOUSE_MODE,
|
|
@@ -5621,6 +5602,7 @@ const createScatterplot = (
|
|
|
5621
5602
|
opacityInactiveMax = DEFAULT_OPACITY_INACTIVE_MAX,
|
|
5622
5603
|
opacityInactiveScale = DEFAULT_OPACITY_INACTIVE_SCALE,
|
|
5623
5604
|
sizeBy = DEFAULT_SIZE_BY,
|
|
5605
|
+
pointOrder = DEFAULT_POINT_ORDER,
|
|
5624
5606
|
pointScaleMode = DEFAULT_POINT_SCALE_MODE,
|
|
5625
5607
|
height = DEFAULT_HEIGHT,
|
|
5626
5608
|
width = DEFAULT_WIDTH,
|
|
@@ -5696,9 +5678,7 @@ const createScatterplot = (
|
|
|
5696
5678
|
let pointConnections;
|
|
5697
5679
|
let pointConnectionMap;
|
|
5698
5680
|
let computingPointConnectionCurves;
|
|
5699
|
-
// biome-ignore lint/style/useNamingConvention: HLine stands for HorizontalLine
|
|
5700
5681
|
let reticleHLine;
|
|
5701
|
-
// biome-ignore lint/style/useNamingConvention: VLine stands for VerticalLine
|
|
5702
5682
|
let reticleVLine;
|
|
5703
5683
|
let computedPointSizeMouseDetection;
|
|
5704
5684
|
let lassoInitiatorTimeout;
|
|
@@ -5831,6 +5811,8 @@ const createScatterplot = (
|
|
|
5831
5811
|
let normalPointsIndexBuffer; // Buffer holding the indices pointing to the correct texel
|
|
5832
5812
|
let selectedPointsIndexBuffer; // Used for pointing to the selected texels
|
|
5833
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
|
|
5834
5816
|
|
|
5835
5817
|
let cameraZoomTargetStart; // Stores the start (i.e., current) camera target for zooming
|
|
5836
5818
|
let cameraZoomTargetEnd; // Stores the end camera target for zooming
|
|
@@ -5853,9 +5835,7 @@ const createScatterplot = (
|
|
|
5853
5835
|
let isAnnotationsDrawn = false;
|
|
5854
5836
|
let isMouseOverCanvasChecked = false;
|
|
5855
5837
|
|
|
5856
|
-
// biome-ignore lint/style/useNamingConvention: ZDate is not one word
|
|
5857
5838
|
let valueZDataType = CATEGORICAL;
|
|
5858
|
-
// biome-ignore lint/style/useNamingConvention: WDate is not one word
|
|
5859
5839
|
let valueWDataType = CATEGORICAL;
|
|
5860
5840
|
|
|
5861
5841
|
/** @type{number|undefined} */
|
|
@@ -5929,9 +5909,7 @@ const createScatterplot = (
|
|
|
5929
5909
|
return points;
|
|
5930
5910
|
};
|
|
5931
5911
|
|
|
5932
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
5933
5912
|
const getPointsInBBox = (x0, y0, x1, y1) => {
|
|
5934
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
5935
5913
|
const pointsInBBox = spatialIndex.range(x0, y0, x1, y1);
|
|
5936
5914
|
if (isPointsFiltered) {
|
|
5937
5915
|
return pointsInBBox.filter((i) => filteredPointsSet.has(i));
|
|
@@ -5946,7 +5924,6 @@ const createScatterplot = (
|
|
|
5946
5924
|
const pointSizeNdc = getPointSizeNdc(4);
|
|
5947
5925
|
|
|
5948
5926
|
// Get all points within a close range
|
|
5949
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
5950
5927
|
const pointsInBBox = getPointsInBBox(
|
|
5951
5928
|
xNdc - pointSizeNdc,
|
|
5952
5929
|
yNdc - pointSizeNdc,
|
|
@@ -5984,7 +5961,6 @@ const createScatterplot = (
|
|
|
5984
5961
|
}
|
|
5985
5962
|
|
|
5986
5963
|
// ...to efficiently preselect potentially selected points
|
|
5987
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
5988
5964
|
const pointsInBBox = getPointsInBBox(...bBox);
|
|
5989
5965
|
// next we test each point in the bounding box if it is in the polygon too
|
|
5990
5966
|
const pointsInPolygon = [];
|
|
@@ -6089,6 +6065,40 @@ const createScatterplot = (
|
|
|
6089
6065
|
}
|
|
6090
6066
|
};
|
|
6091
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
|
+
|
|
6092
6102
|
/**
|
|
6093
6103
|
* Select and highlight a set of points
|
|
6094
6104
|
* @param {number | number[]} pointIdxs
|
|
@@ -6192,6 +6202,47 @@ const createScatterplot = (
|
|
|
6192
6202
|
draw = true;
|
|
6193
6203
|
};
|
|
6194
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
|
+
|
|
6195
6246
|
/**
|
|
6196
6247
|
* @param {number} point
|
|
6197
6248
|
* @param {import('./types').ScatterplotMethodOptions['hover']} options
|
|
@@ -6868,6 +6919,44 @@ const createScatterplot = (
|
|
|
6868
6919
|
const setSizeBy = (type) => {
|
|
6869
6920
|
sizeBy = getEncodingType(type, DEFAULT_SIZE_BY);
|
|
6870
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
|
+
};
|
|
6871
6960
|
const setPointConnectionColorBy = (type) => {
|
|
6872
6961
|
pointConnectionColorBy = getEncodingType(
|
|
6873
6962
|
type,
|
|
@@ -7035,16 +7124,14 @@ const createScatterplot = (
|
|
|
7035
7124
|
getPointOpacityScale = getPointOpacityScaleBase,
|
|
7036
7125
|
) =>
|
|
7037
7126
|
renderer.regl({
|
|
7038
|
-
frag: renderPointsAsSquares ? FRAGMENT_SHADER
|
|
7127
|
+
frag: renderPointsAsSquares ? FRAGMENT_SHADER : FRAGMENT_SHADER$1,
|
|
7039
7128
|
vert: createVertexShader(globalState),
|
|
7040
7129
|
|
|
7041
7130
|
blend: {
|
|
7042
7131
|
enable: !disableAlphaBlending,
|
|
7043
7132
|
func: {
|
|
7044
|
-
// biome-ignore lint/style/useNamingConvention: Regl specific
|
|
7045
7133
|
srcRGB: 'src alpha',
|
|
7046
7134
|
srcAlpha: 'one',
|
|
7047
|
-
// biome-ignore lint/style/useNamingConvention: Regl specific
|
|
7048
7135
|
dstRGB: 'one minus src alpha',
|
|
7049
7136
|
dstAlpha: 'one minus src alpha',
|
|
7050
7137
|
},
|
|
@@ -7185,10 +7272,8 @@ const createScatterplot = (
|
|
|
7185
7272
|
blend: {
|
|
7186
7273
|
enable: true,
|
|
7187
7274
|
func: {
|
|
7188
|
-
// biome-ignore lint/style/useNamingConvention: Regl specific
|
|
7189
7275
|
srcRGB: 'src alpha',
|
|
7190
7276
|
srcAlpha: 'one',
|
|
7191
|
-
// biome-ignore lint/style/useNamingConvention: Regl specific
|
|
7192
7277
|
dstRGB: 'one minus src alpha',
|
|
7193
7278
|
dstAlpha: 'one minus src alpha',
|
|
7194
7279
|
},
|
|
@@ -7266,6 +7351,51 @@ const createScatterplot = (
|
|
|
7266
7351
|
return index;
|
|
7267
7352
|
};
|
|
7268
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
|
+
|
|
7269
7399
|
const createStateTexture = (newPoints, dataTypes = {}) => {
|
|
7270
7400
|
const numNewPoints = newPoints.length;
|
|
7271
7401
|
stateTexRes = Math.max(2, Math.ceil(Math.sqrt(numNewPoints)));
|
|
@@ -7357,9 +7487,18 @@ const createScatterplot = (
|
|
|
7357
7487
|
const preventFilterReset =
|
|
7358
7488
|
options?.preventFilterReset && newPoints.length === numPoints;
|
|
7359
7489
|
|
|
7490
|
+
const prevNumPoints = numPoints;
|
|
7360
7491
|
numPoints = newPoints.length;
|
|
7361
7492
|
numPointsInView = numPoints;
|
|
7362
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
|
+
|
|
7363
7502
|
if (stateTex) {
|
|
7364
7503
|
stateTex.destroy();
|
|
7365
7504
|
}
|
|
@@ -7369,10 +7508,11 @@ const createScatterplot = (
|
|
|
7369
7508
|
});
|
|
7370
7509
|
|
|
7371
7510
|
if (!preventFilterReset) {
|
|
7511
|
+
computePointOrderIndex();
|
|
7372
7512
|
normalPointsIndexBuffer({
|
|
7373
7513
|
usage: 'static',
|
|
7374
7514
|
type: 'float',
|
|
7375
|
-
data:
|
|
7515
|
+
data: getEffectivePointIndex(numPoints),
|
|
7376
7516
|
});
|
|
7377
7517
|
}
|
|
7378
7518
|
|
|
@@ -7613,7 +7753,7 @@ const createScatterplot = (
|
|
|
7613
7753
|
const unfilter = ({ preventEvent = false } = {}) => {
|
|
7614
7754
|
isPointsFiltered = false;
|
|
7615
7755
|
filteredPointsSet.clear();
|
|
7616
|
-
normalPointsIndexBuffer.subdata(
|
|
7756
|
+
normalPointsIndexBuffer.subdata(getEffectivePointIndex(numPoints));
|
|
7617
7757
|
|
|
7618
7758
|
return new Promise((resolve) => {
|
|
7619
7759
|
const finish = () => {
|
|
@@ -7672,9 +7812,20 @@ const createScatterplot = (
|
|
|
7672
7812
|
}
|
|
7673
7813
|
}
|
|
7674
7814
|
|
|
7675
|
-
|
|
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
|
+
}
|
|
7676
7827
|
|
|
7677
|
-
for (const pointIdx of
|
|
7828
|
+
for (const pointIdx of orderedFilteredPoints) {
|
|
7678
7829
|
filteredPointsBuffer.push.apply(
|
|
7679
7830
|
filteredPointsBuffer,
|
|
7680
7831
|
indexToStateTexCoord(pointIdx),
|
|
@@ -8061,7 +8212,7 @@ const createScatterplot = (
|
|
|
8061
8212
|
continue;
|
|
8062
8213
|
}
|
|
8063
8214
|
|
|
8064
|
-
if (
|
|
8215
|
+
if (isPolygonAnnotation(annotation)) {
|
|
8065
8216
|
newPoints.push(annotation.vertices.flatMap(identity));
|
|
8066
8217
|
addColorAndWidth(annotation);
|
|
8067
8218
|
}
|
|
@@ -8102,7 +8253,6 @@ const createScatterplot = (
|
|
|
8102
8253
|
* @param {number[]} pointIdxs - A list of point indices
|
|
8103
8254
|
* @returns {import('./types').Rect} The bounding box
|
|
8104
8255
|
*/
|
|
8105
|
-
// biome-ignore lint/style/useNamingConvention: BBox stands for BoundingBox
|
|
8106
8256
|
const getBBoxOfPoints = (pointIdxs) => {
|
|
8107
8257
|
let xMin = Number.POSITIVE_INFINITY;
|
|
8108
8258
|
let xMax = Number.NEGATIVE_INFINITY;
|
|
@@ -8138,7 +8288,6 @@ const createScatterplot = (
|
|
|
8138
8288
|
// Vertical field of view
|
|
8139
8289
|
// The Arc Tangent is based on the original camera position. Otherwise
|
|
8140
8290
|
// we would have to do `Math.atan(1 / camera.view[5])`
|
|
8141
|
-
// biome-ignore lint/style/useNamingConvention: FOV stands for field of view
|
|
8142
8291
|
const vFOV = 2 * Math.atan(1);
|
|
8143
8292
|
|
|
8144
8293
|
const aspectRatio = viewAspectRatio / dataAspectRatio;
|
|
@@ -8538,7 +8687,6 @@ const createScatterplot = (
|
|
|
8538
8687
|
reticleVLine.setStyle({ color: reticleColor });
|
|
8539
8688
|
};
|
|
8540
8689
|
|
|
8541
|
-
// biome-ignore lint/style/useNamingConvention: XScale are two words
|
|
8542
8690
|
const setXScale = (newXScale) => {
|
|
8543
8691
|
if (!newXScale) {
|
|
8544
8692
|
return;
|
|
@@ -8551,7 +8699,6 @@ const createScatterplot = (
|
|
|
8551
8699
|
updateScales();
|
|
8552
8700
|
};
|
|
8553
8701
|
|
|
8554
|
-
// biome-ignore lint/style/useNamingConvention: YScale are two words
|
|
8555
8702
|
const setYScale = (newYScale) => {
|
|
8556
8703
|
if (!newYScale) {
|
|
8557
8704
|
return;
|
|
@@ -8725,7 +8872,6 @@ const createScatterplot = (
|
|
|
8725
8872
|
annotationLineWidth = +newAnnotationLineWidth;
|
|
8726
8873
|
};
|
|
8727
8874
|
|
|
8728
|
-
// biome-ignore lint/style/useNamingConvention: HVLine stands for horizontal vertical line
|
|
8729
8875
|
const setAnnotationHVLineLimit = (newAnnotationHVLineLimit) => {
|
|
8730
8876
|
annotationHVLineLimit = +newAnnotationHVLineLimit;
|
|
8731
8877
|
};
|
|
@@ -8802,6 +8948,10 @@ const createScatterplot = (
|
|
|
8802
8948
|
return sizeBy;
|
|
8803
8949
|
}
|
|
8804
8950
|
|
|
8951
|
+
if (property === 'pointOrder') {
|
|
8952
|
+
return pointOrder !== null ? [...pointOrder] : null;
|
|
8953
|
+
}
|
|
8954
|
+
|
|
8805
8955
|
if (property === 'deselectOnDblClick') {
|
|
8806
8956
|
return deselectOnDblClick;
|
|
8807
8957
|
}
|
|
@@ -9188,6 +9338,10 @@ const createScatterplot = (
|
|
|
9188
9338
|
setSizeBy(properties.sizeBy);
|
|
9189
9339
|
}
|
|
9190
9340
|
|
|
9341
|
+
if (properties.pointOrder !== undefined) {
|
|
9342
|
+
setPointOrder(properties.pointOrder);
|
|
9343
|
+
}
|
|
9344
|
+
|
|
9191
9345
|
if (properties.opacity !== undefined) {
|
|
9192
9346
|
setOpacity(properties.opacity);
|
|
9193
9347
|
}
|
|
@@ -9885,6 +10039,9 @@ const createScatterplot = (
|
|
|
9885
10039
|
init();
|
|
9886
10040
|
|
|
9887
10041
|
return {
|
|
10042
|
+
attachSpatialIndex: (buffer) => {
|
|
10043
|
+
spatialIndex = kdbushFrom(buffer);
|
|
10044
|
+
},
|
|
9888
10045
|
/**
|
|
9889
10046
|
* Get whether the browser supports all necessary WebGL features
|
|
9890
10047
|
* @return {boolean} If `true` the browser supports all necessary WebGL features
|
|
@@ -9908,6 +10065,7 @@ const createScatterplot = (
|
|
|
9908
10065
|
get,
|
|
9909
10066
|
getScreenPosition,
|
|
9910
10067
|
hover,
|
|
10068
|
+
lassoSelect,
|
|
9911
10069
|
redraw,
|
|
9912
10070
|
refresh: renderer.refresh,
|
|
9913
10071
|
reset: withDraw(reset),
|