@hema-to/regl-scatterplot 1.14.1-hemato.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1110 -0
- package/dist/regl-scatterplot.esm.d.ts +126 -0
- package/dist/regl-scatterplot.esm.js +9962 -0
- package/dist/regl-scatterplot.js +9385 -0
- package/dist/regl-scatterplot.min.js +8 -0
- package/dist/types.d.ts +306 -0
- package/package.json +100 -0
- package/src/bg.fs +13 -0
- package/src/bg.vs +16 -0
- package/src/constants.js +189 -0
- package/src/index.js +4654 -0
- package/src/kdbush-class.js +369 -0
- package/src/kdbush-worker.js +20 -0
- package/src/kdbush.js +69 -0
- package/src/lasso-manager/constants.js +7 -0
- package/src/lasso-manager/create-long-press-animations.js +257 -0
- package/src/lasso-manager/create-long-press-elements.js +68 -0
- package/src/lasso-manager/index.js +781 -0
- package/src/lasso-manager/utils.js +40 -0
- package/src/point-simple.fs +10 -0
- package/src/point-update.fs +21 -0
- package/src/point-update.vs +13 -0
- package/src/point.fs +22 -0
- package/src/point.vs +124 -0
- package/src/renderer.js +252 -0
- package/src/spline-curve-worker.js +271 -0
- package/src/spline-curve.js +23 -0
- package/src/types.d.ts +306 -0
- package/src/utils.js +598 -0
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assign,
|
|
3
|
+
identity,
|
|
4
|
+
l2Norm,
|
|
5
|
+
l2PointDist,
|
|
6
|
+
nextAnimationFrame,
|
|
7
|
+
pipe,
|
|
8
|
+
throttleAndDebounce,
|
|
9
|
+
withConstructor,
|
|
10
|
+
withStaticProperty,
|
|
11
|
+
} from '@flekschas/utils';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_BRUSH_SIZE,
|
|
15
|
+
DEFAULT_LASSO_MIN_DELAY,
|
|
16
|
+
DEFAULT_LASSO_MIN_DIST,
|
|
17
|
+
DEFAULT_LASSO_START_INITIATOR_SHOW,
|
|
18
|
+
DEFAULT_LASSO_TYPE,
|
|
19
|
+
LASSO_HIDE_START_INITIATOR_TIME,
|
|
20
|
+
LASSO_SHOW_START_INITIATOR_TIME,
|
|
21
|
+
} from './constants.js';
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
DEFAULT_LASSO_LONG_PRESS_AFTER_EFFECT_TIME,
|
|
25
|
+
DEFAULT_LASSO_LONG_PRESS_EFFECT_DELAY,
|
|
26
|
+
DEFAULT_LASSO_LONG_PRESS_REVERT_EFFECT_TIME,
|
|
27
|
+
DEFAULT_LASSO_LONG_PRESS_TIME,
|
|
28
|
+
} from '../constants.js';
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
createLongPressInAnimations,
|
|
32
|
+
createLongPressOutAnimations,
|
|
33
|
+
} from './create-long-press-animations.js';
|
|
34
|
+
import createLongPressElements from './create-long-press-elements.js';
|
|
35
|
+
import { exponentialMovingAverage } from './utils.js';
|
|
36
|
+
|
|
37
|
+
const ifNotNull = (v, alternative = null) => (v === null ? alternative : v);
|
|
38
|
+
|
|
39
|
+
let cachedLassoStylesheets;
|
|
40
|
+
|
|
41
|
+
const getLassoStylesheets = () => {
|
|
42
|
+
if (!cachedLassoStylesheets) {
|
|
43
|
+
const lassoStyleEl = document.createElement('style');
|
|
44
|
+
document.head.appendChild(lassoStyleEl);
|
|
45
|
+
cachedLassoStylesheets = lassoStyleEl.sheet;
|
|
46
|
+
}
|
|
47
|
+
return cachedLassoStylesheets;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const addRule = (rule) => {
|
|
51
|
+
const lassoStylesheets = getLassoStylesheets();
|
|
52
|
+
const currentNumRules = lassoStylesheets.rules.length;
|
|
53
|
+
lassoStylesheets.insertRule(rule, currentNumRules);
|
|
54
|
+
return currentNumRules;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const removeRule = (index) => {
|
|
58
|
+
getLassoStylesheets().deleteRule(index);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const inAnimation = `${LASSO_SHOW_START_INITIATOR_TIME}ms ease scaleInFadeOut 0s 1 normal backwards`;
|
|
62
|
+
|
|
63
|
+
const createInAnimationRule = (opacity, scale, rotate) => `
|
|
64
|
+
@keyframes scaleInFadeOut {
|
|
65
|
+
0% {
|
|
66
|
+
opacity: ${opacity};
|
|
67
|
+
transform: translate(-50%,-50%) scale(${scale}) rotate(${rotate}deg);
|
|
68
|
+
}
|
|
69
|
+
10% {
|
|
70
|
+
opacity: 1;
|
|
71
|
+
transform: translate(-50%,-50%) scale(1) rotate(${rotate + 20}deg);
|
|
72
|
+
}
|
|
73
|
+
100% {
|
|
74
|
+
opacity: 0;
|
|
75
|
+
transform: translate(-50%,-50%) scale(0.9) rotate(${rotate + 60}deg);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
let inAnimationRuleIndex = null;
|
|
80
|
+
|
|
81
|
+
const outAnimation = `${LASSO_HIDE_START_INITIATOR_TIME}ms ease fadeScaleOut 0s 1 normal backwards`;
|
|
82
|
+
|
|
83
|
+
const createOutAnimationRule = (opacity, scale, rotate) => `
|
|
84
|
+
@keyframes fadeScaleOut {
|
|
85
|
+
0% {
|
|
86
|
+
opacity: ${opacity};
|
|
87
|
+
transform: translate(-50%,-50%) scale(${scale}) rotate(${rotate}deg);
|
|
88
|
+
}
|
|
89
|
+
100% {
|
|
90
|
+
opacity: 0;
|
|
91
|
+
transform: translate(-50%,-50%) scale(0) rotate(${rotate}deg);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
let outAnimationRuleIndex = null;
|
|
96
|
+
|
|
97
|
+
export const createLasso = (
|
|
98
|
+
element,
|
|
99
|
+
{
|
|
100
|
+
onDraw: initialOnDraw = identity,
|
|
101
|
+
onStart: initialOnStart = identity,
|
|
102
|
+
onEnd: initialOnEnd = identity,
|
|
103
|
+
enableInitiator:
|
|
104
|
+
initialenableInitiator = DEFAULT_LASSO_START_INITIATOR_SHOW,
|
|
105
|
+
initiatorParentElement: initialInitiatorParentElement = document.body,
|
|
106
|
+
longPressIndicatorParentElement:
|
|
107
|
+
initialLongPressIndicatorParentElement = document.body,
|
|
108
|
+
minDelay: initialMinDelay = DEFAULT_LASSO_MIN_DELAY,
|
|
109
|
+
minDist: initialMinDist = DEFAULT_LASSO_MIN_DIST,
|
|
110
|
+
pointNorm: initialPointNorm = identity,
|
|
111
|
+
type: initialType = DEFAULT_LASSO_TYPE,
|
|
112
|
+
brushSize: initialBrushSize = DEFAULT_BRUSH_SIZE,
|
|
113
|
+
} = {},
|
|
114
|
+
) => {
|
|
115
|
+
let enableInitiator = initialenableInitiator;
|
|
116
|
+
let initiatorParentElement = initialInitiatorParentElement;
|
|
117
|
+
let longPressIndicatorParentElement = initialLongPressIndicatorParentElement;
|
|
118
|
+
|
|
119
|
+
let onDraw = initialOnDraw;
|
|
120
|
+
let onStart = initialOnStart;
|
|
121
|
+
let onEnd = initialOnEnd;
|
|
122
|
+
|
|
123
|
+
let minDelay = initialMinDelay;
|
|
124
|
+
let minDist = initialMinDist;
|
|
125
|
+
|
|
126
|
+
let pointNorm = initialPointNorm;
|
|
127
|
+
|
|
128
|
+
let type = initialType;
|
|
129
|
+
let brushSize = initialBrushSize;
|
|
130
|
+
|
|
131
|
+
const initiator = document.createElement('div');
|
|
132
|
+
const initiatorId =
|
|
133
|
+
Math.random().toString(36).substring(2, 5) +
|
|
134
|
+
Math.random().toString(36).substring(2, 5);
|
|
135
|
+
initiator.id = `lasso-initiator-${initiatorId}`;
|
|
136
|
+
initiator.style.position = 'fixed';
|
|
137
|
+
initiator.style.display = 'flex';
|
|
138
|
+
initiator.style.justifyContent = 'center';
|
|
139
|
+
initiator.style.alignItems = 'center';
|
|
140
|
+
initiator.style.zIndex = 99;
|
|
141
|
+
initiator.style.width = '4rem';
|
|
142
|
+
initiator.style.height = '4rem';
|
|
143
|
+
initiator.style.borderRadius = '4rem';
|
|
144
|
+
initiator.style.opacity = 0.5;
|
|
145
|
+
initiator.style.transform = 'translate(-50%,-50%) scale(0) rotate(0deg)';
|
|
146
|
+
|
|
147
|
+
const {
|
|
148
|
+
longPress,
|
|
149
|
+
longPressCircle,
|
|
150
|
+
longPressCircleLeft,
|
|
151
|
+
longPressCircleRight,
|
|
152
|
+
longPressEffect,
|
|
153
|
+
} = createLongPressElements();
|
|
154
|
+
|
|
155
|
+
let isMouseDown = false;
|
|
156
|
+
let isLasso = false;
|
|
157
|
+
let lassoPos = [];
|
|
158
|
+
let lassoPosFlat = [];
|
|
159
|
+
let lassoBrushCenterPos = [];
|
|
160
|
+
let lassoBrushNormals = [];
|
|
161
|
+
let prevMousePos;
|
|
162
|
+
let longPressIsStarting = false;
|
|
163
|
+
|
|
164
|
+
let longPressMainInAnimationRuleIndex = null;
|
|
165
|
+
let longPressEffectInAnimationRuleIndex = null;
|
|
166
|
+
let longPressCircleLeftInAnimationRuleIndex = null;
|
|
167
|
+
let longPressCircleRightInAnimationRuleIndex = null;
|
|
168
|
+
let longPressCircleInAnimationRuleIndex = null;
|
|
169
|
+
let longPressMainOutAnimationRuleIndex = null;
|
|
170
|
+
let longPressEffectOutAnimationRuleIndex = null;
|
|
171
|
+
let longPressCircleLeftOutAnimationRuleIndex = null;
|
|
172
|
+
let longPressCircleRightOutAnimationRuleIndex = null;
|
|
173
|
+
let longPressCircleOutAnimationRuleIndex = null;
|
|
174
|
+
|
|
175
|
+
const mouseUpHandler = () => {
|
|
176
|
+
isMouseDown = false;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const getMousePosition = (event) => {
|
|
180
|
+
const { left, top } = element.getBoundingClientRect();
|
|
181
|
+
|
|
182
|
+
return [event.clientX - left, event.clientY - top];
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
window.addEventListener('mouseup', mouseUpHandler);
|
|
186
|
+
|
|
187
|
+
const resetInitiatorStyle = () => {
|
|
188
|
+
initiator.style.opacity = 0.5;
|
|
189
|
+
initiator.style.transform = 'translate(-50%,-50%) scale(0) rotate(0deg)';
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const getCurrentTransformStyle = (node, hasRotated) => {
|
|
193
|
+
const computedStyle = getComputedStyle(node);
|
|
194
|
+
const opacity = +computedStyle.opacity;
|
|
195
|
+
// The css rule `transform: translate(-1, -1) scale(0.5);` is represented as
|
|
196
|
+
// `matrix(0.5, 0, 0, 0.5, -1, -1)`
|
|
197
|
+
const m = computedStyle.transform.match(/([0-9.-]+)+/g);
|
|
198
|
+
|
|
199
|
+
const a = +m[0];
|
|
200
|
+
const b = +m[1];
|
|
201
|
+
|
|
202
|
+
const scale = Math.sqrt(a * a + b * b);
|
|
203
|
+
let rotate = Math.atan2(b, a) * (180 / Math.PI);
|
|
204
|
+
|
|
205
|
+
rotate = hasRotated && rotate <= 0 ? 360 + rotate : rotate;
|
|
206
|
+
|
|
207
|
+
return { opacity, scale, rotate };
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const showInitiator = (event) => {
|
|
211
|
+
if (!enableInitiator || isMouseDown) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const x = event.clientX;
|
|
216
|
+
const y = event.clientY;
|
|
217
|
+
initiator.style.top = `${y}px`;
|
|
218
|
+
initiator.style.left = `${x}px`;
|
|
219
|
+
|
|
220
|
+
const style = getCurrentTransformStyle(initiator);
|
|
221
|
+
const opacity = style.opacity;
|
|
222
|
+
const scale = style.scale;
|
|
223
|
+
const rotate = style.rotate;
|
|
224
|
+
initiator.style.opacity = opacity;
|
|
225
|
+
initiator.style.transform = `translate(-50%,-50%) scale(${scale}) rotate(${rotate}deg)`;
|
|
226
|
+
|
|
227
|
+
initiator.style.animation = 'none';
|
|
228
|
+
|
|
229
|
+
// See https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Tips
|
|
230
|
+
// why we need to wait for two animation frames
|
|
231
|
+
nextAnimationFrame().then(() => {
|
|
232
|
+
if (inAnimationRuleIndex !== null) {
|
|
233
|
+
removeRule(inAnimationRuleIndex);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
inAnimationRuleIndex = addRule(
|
|
237
|
+
createInAnimationRule(opacity, scale, rotate),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
initiator.style.animation = inAnimation;
|
|
241
|
+
|
|
242
|
+
nextAnimationFrame().then(() => {
|
|
243
|
+
resetInitiatorStyle();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const hideInitiator = () => {
|
|
249
|
+
const { opacity, scale, rotate } = getCurrentTransformStyle(initiator);
|
|
250
|
+
initiator.style.opacity = opacity;
|
|
251
|
+
initiator.style.transform = `translate(-50%,-50%) scale(${scale}) rotate(${rotate}deg)`;
|
|
252
|
+
|
|
253
|
+
initiator.style.animation = 'none';
|
|
254
|
+
|
|
255
|
+
nextAnimationFrame(2).then(() => {
|
|
256
|
+
if (outAnimationRuleIndex !== null) {
|
|
257
|
+
removeRule(outAnimationRuleIndex);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
outAnimationRuleIndex = addRule(
|
|
261
|
+
createOutAnimationRule(opacity, scale, rotate),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
initiator.style.animation = outAnimation;
|
|
265
|
+
|
|
266
|
+
nextAnimationFrame().then(() => {
|
|
267
|
+
resetInitiatorStyle();
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const showLongPressIndicator = (
|
|
273
|
+
x,
|
|
274
|
+
y,
|
|
275
|
+
{
|
|
276
|
+
time = DEFAULT_LASSO_LONG_PRESS_TIME,
|
|
277
|
+
extraTime = DEFAULT_LASSO_LONG_PRESS_AFTER_EFFECT_TIME,
|
|
278
|
+
delay = DEFAULT_LASSO_LONG_PRESS_EFFECT_DELAY,
|
|
279
|
+
} = {
|
|
280
|
+
time: DEFAULT_LASSO_LONG_PRESS_TIME,
|
|
281
|
+
extraTime: DEFAULT_LASSO_LONG_PRESS_AFTER_EFFECT_TIME,
|
|
282
|
+
delay: DEFAULT_LASSO_LONG_PRESS_EFFECT_DELAY,
|
|
283
|
+
},
|
|
284
|
+
) => {
|
|
285
|
+
longPressIsStarting = true;
|
|
286
|
+
|
|
287
|
+
const mainStyle = getComputedStyle(longPress);
|
|
288
|
+
longPress.style.color = mainStyle.color;
|
|
289
|
+
longPress.style.top = `${y}px`;
|
|
290
|
+
longPress.style.left = `${x}px`;
|
|
291
|
+
longPress.style.animation = 'none';
|
|
292
|
+
|
|
293
|
+
const circleStyle = getComputedStyle(longPressCircle);
|
|
294
|
+
longPressCircle.style.clipPath = circleStyle.clipPath;
|
|
295
|
+
longPressCircle.style.opacity = circleStyle.opacity;
|
|
296
|
+
longPressCircle.style.animation = 'none';
|
|
297
|
+
|
|
298
|
+
const effectStyle = getCurrentTransformStyle(longPressEffect);
|
|
299
|
+
longPressEffect.style.opacity = effectStyle.opacity;
|
|
300
|
+
longPressEffect.style.transform = `scale(${effectStyle.scale})`;
|
|
301
|
+
longPressEffect.style.animation = 'none';
|
|
302
|
+
|
|
303
|
+
const circleLeftStyle = getCurrentTransformStyle(longPressCircleLeft);
|
|
304
|
+
longPressCircleLeft.style.transform = `rotate(${circleLeftStyle.rotate}deg)`;
|
|
305
|
+
longPressCircleLeft.style.animation = 'none';
|
|
306
|
+
|
|
307
|
+
const circleRightStyle = getCurrentTransformStyle(longPressCircleRight);
|
|
308
|
+
longPressCircleRight.style.transform = `rotate(${circleRightStyle.rotate}deg)`;
|
|
309
|
+
longPressCircleRight.style.animation = 'none';
|
|
310
|
+
|
|
311
|
+
nextAnimationFrame().then(() => {
|
|
312
|
+
if (!longPressIsStarting) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (longPressCircleInAnimationRuleIndex !== null) {
|
|
317
|
+
removeRule(longPressCircleInAnimationRuleIndex);
|
|
318
|
+
}
|
|
319
|
+
if (longPressCircleRightInAnimationRuleIndex !== null) {
|
|
320
|
+
removeRule(longPressCircleRightInAnimationRuleIndex);
|
|
321
|
+
}
|
|
322
|
+
if (longPressCircleLeftInAnimationRuleIndex !== null) {
|
|
323
|
+
removeRule(longPressCircleLeftInAnimationRuleIndex);
|
|
324
|
+
}
|
|
325
|
+
if (longPressEffectInAnimationRuleIndex !== null) {
|
|
326
|
+
removeRule(longPressEffectInAnimationRuleIndex);
|
|
327
|
+
}
|
|
328
|
+
if (longPressMainInAnimationRuleIndex !== null) {
|
|
329
|
+
removeRule(longPressMainInAnimationRuleIndex);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const { rules, names } = createLongPressInAnimations({
|
|
333
|
+
time,
|
|
334
|
+
extraTime,
|
|
335
|
+
delay,
|
|
336
|
+
currentColor: mainStyle.color || 'currentcolor',
|
|
337
|
+
targetColor: longPress.dataset.activeColor,
|
|
338
|
+
effectOpacity: effectStyle.opacity || 0,
|
|
339
|
+
effectScale: effectStyle.scale || 0,
|
|
340
|
+
circleLeftRotation: circleLeftStyle.rotate || 0,
|
|
341
|
+
circleRightRotation: circleRightStyle.rotate || 0,
|
|
342
|
+
circleClipPath: circleStyle.clipPath || 'inset(0 0 0 50%)',
|
|
343
|
+
circleOpacity: circleStyle.opacity || 0,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
longPressMainInAnimationRuleIndex = addRule(rules.main);
|
|
347
|
+
longPressEffectInAnimationRuleIndex = addRule(rules.effect);
|
|
348
|
+
longPressCircleLeftInAnimationRuleIndex = addRule(rules.circleLeft);
|
|
349
|
+
longPressCircleRightInAnimationRuleIndex = addRule(rules.circleRight);
|
|
350
|
+
longPressCircleInAnimationRuleIndex = addRule(rules.circle);
|
|
351
|
+
|
|
352
|
+
longPress.style.animation = names.main;
|
|
353
|
+
longPressEffect.style.animation = names.effect;
|
|
354
|
+
longPressCircleLeft.style.animation = names.circleLeft;
|
|
355
|
+
longPressCircleRight.style.animation = names.circleRight;
|
|
356
|
+
longPressCircle.style.animation = names.circle;
|
|
357
|
+
});
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const hideLongPressIndicator = (
|
|
361
|
+
{ time = DEFAULT_LASSO_LONG_PRESS_REVERT_EFFECT_TIME } = {
|
|
362
|
+
time: DEFAULT_LASSO_LONG_PRESS_REVERT_EFFECT_TIME,
|
|
363
|
+
},
|
|
364
|
+
) => {
|
|
365
|
+
if (!longPressIsStarting) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
longPressIsStarting = false;
|
|
370
|
+
|
|
371
|
+
const mainStyle = getComputedStyle(longPress);
|
|
372
|
+
longPress.style.color = mainStyle.color;
|
|
373
|
+
longPress.style.animation = 'none';
|
|
374
|
+
|
|
375
|
+
const circleStyle = getComputedStyle(longPressCircle);
|
|
376
|
+
longPressCircle.style.clipPath = circleStyle.clipPath;
|
|
377
|
+
longPressCircle.style.opacity = circleStyle.opacity;
|
|
378
|
+
longPressCircle.style.animation = 'none';
|
|
379
|
+
|
|
380
|
+
const effectStyle = getCurrentTransformStyle(longPressEffect);
|
|
381
|
+
longPressEffect.style.opacity = effectStyle.opacity;
|
|
382
|
+
longPressEffect.style.transform = `scale(${effectStyle.scale})`;
|
|
383
|
+
longPressEffect.style.animation = 'none';
|
|
384
|
+
|
|
385
|
+
// The first half of the circle animation, the clip-path is set to `inset(0px 0px 0px 50%)`.
|
|
386
|
+
// In the second half it's set to `inset(0px)`. Hence we can look at the second to last
|
|
387
|
+
// character to determine if the animatation has progressed passed half time.
|
|
388
|
+
const isAnimatedMoreThan50Percent =
|
|
389
|
+
circleStyle.clipPath.slice(-2, -1) === 'x';
|
|
390
|
+
|
|
391
|
+
const circleLeftStyle = getCurrentTransformStyle(
|
|
392
|
+
longPressCircleLeft,
|
|
393
|
+
isAnimatedMoreThan50Percent,
|
|
394
|
+
);
|
|
395
|
+
longPressCircleLeft.style.transform = `rotate(${circleLeftStyle.rotate}deg)`;
|
|
396
|
+
longPressCircleLeft.style.animation = 'none';
|
|
397
|
+
|
|
398
|
+
const circleRightStyle = getCurrentTransformStyle(longPressCircleRight);
|
|
399
|
+
longPressCircleRight.style.transform = `rotate(${circleRightStyle.rotate}deg)`;
|
|
400
|
+
longPressCircleRight.style.animation = 'none';
|
|
401
|
+
|
|
402
|
+
nextAnimationFrame().then(() => {
|
|
403
|
+
if (longPressCircleOutAnimationRuleIndex !== null) {
|
|
404
|
+
removeRule(longPressCircleOutAnimationRuleIndex);
|
|
405
|
+
}
|
|
406
|
+
if (longPressCircleRightOutAnimationRuleIndex !== null) {
|
|
407
|
+
removeRule(longPressCircleRightOutAnimationRuleIndex);
|
|
408
|
+
}
|
|
409
|
+
if (longPressCircleLeftOutAnimationRuleIndex !== null) {
|
|
410
|
+
removeRule(longPressCircleLeftOutAnimationRuleIndex);
|
|
411
|
+
}
|
|
412
|
+
if (longPressEffectOutAnimationRuleIndex !== null) {
|
|
413
|
+
removeRule(longPressEffectOutAnimationRuleIndex);
|
|
414
|
+
}
|
|
415
|
+
if (longPressMainOutAnimationRuleIndex !== null) {
|
|
416
|
+
removeRule(longPressMainOutAnimationRuleIndex);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const { rules, names } = createLongPressOutAnimations({
|
|
420
|
+
time,
|
|
421
|
+
currentColor: mainStyle.color || 'currentcolor',
|
|
422
|
+
targetColor: longPress.dataset.color,
|
|
423
|
+
effectOpacity: effectStyle.opacity || 0,
|
|
424
|
+
effectScale: effectStyle.scale || 0,
|
|
425
|
+
circleLeftRotation: circleLeftStyle.rotate || 0,
|
|
426
|
+
circleRightRotation: circleRightStyle.rotate || 0,
|
|
427
|
+
circleClipPath: circleStyle.clipPath || 'inset(0px)',
|
|
428
|
+
circleOpacity: circleStyle.opacity || 1,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
longPressMainOutAnimationRuleIndex = addRule(rules.main);
|
|
432
|
+
longPressEffectOutAnimationRuleIndex = addRule(rules.effect);
|
|
433
|
+
longPressCircleLeftOutAnimationRuleIndex = addRule(rules.circleLeft);
|
|
434
|
+
longPressCircleRightOutAnimationRuleIndex = addRule(rules.circleRight);
|
|
435
|
+
longPressCircleOutAnimationRuleIndex = addRule(rules.circle);
|
|
436
|
+
|
|
437
|
+
longPress.style.animation = names.main;
|
|
438
|
+
longPressEffect.style.animation = names.effect;
|
|
439
|
+
longPressCircleLeft.style.animation = names.circleLeft;
|
|
440
|
+
longPressCircleRight.style.animation = names.circleRight;
|
|
441
|
+
longPressCircle.style.animation = names.circle;
|
|
442
|
+
});
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const draw = () => {
|
|
446
|
+
onDraw(lassoPos, lassoPosFlat);
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const extendFreeform = (point) => {
|
|
450
|
+
lassoPos.push(point);
|
|
451
|
+
lassoPosFlat.push(point[0], point[1]);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const extendRectangle = (point) => {
|
|
455
|
+
const [x, y] = point;
|
|
456
|
+
const [startX, startY] = lassoPos[0];
|
|
457
|
+
|
|
458
|
+
lassoPos[1] = [x, startY];
|
|
459
|
+
lassoPos[2] = [x, y];
|
|
460
|
+
lassoPos[3] = [startX, y];
|
|
461
|
+
lassoPos[4] = [startX, startY];
|
|
462
|
+
|
|
463
|
+
lassoPosFlat[2] = x;
|
|
464
|
+
lassoPosFlat[3] = startY;
|
|
465
|
+
lassoPosFlat[4] = x;
|
|
466
|
+
lassoPosFlat[5] = y;
|
|
467
|
+
lassoPosFlat[6] = startX;
|
|
468
|
+
lassoPosFlat[7] = y;
|
|
469
|
+
lassoPosFlat[8] = startX;
|
|
470
|
+
lassoPosFlat[9] = startY;
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const startBrush = (point) => {
|
|
474
|
+
lassoBrushCenterPos.push(point);
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const getNormalizedBrushSize = () =>
|
|
478
|
+
Math.abs(pointNorm([0, 0])[0] - pointNorm([brushSize / 2, 0])[0]);
|
|
479
|
+
|
|
480
|
+
const getBrushNormal = (point1, point2, w) => {
|
|
481
|
+
const [x1, y1] = point1;
|
|
482
|
+
const [x2, y2] = point2;
|
|
483
|
+
|
|
484
|
+
const dx = x1 - x2;
|
|
485
|
+
const dy = y1 - y2;
|
|
486
|
+
const dn = l2Norm([dx, dy]);
|
|
487
|
+
|
|
488
|
+
return [(+dy / dn) * w, (-dx / dn) * w];
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const extendBrush = (point) => {
|
|
492
|
+
const prevPoint = lassoBrushCenterPos.at(-1);
|
|
493
|
+
|
|
494
|
+
const width = getNormalizedBrushSize();
|
|
495
|
+
let [nx, ny] = getBrushNormal(point, prevPoint, width);
|
|
496
|
+
|
|
497
|
+
const N = lassoBrushCenterPos.length;
|
|
498
|
+
|
|
499
|
+
if (N === 1) {
|
|
500
|
+
// In this special case, we have to add the initial two points and normal
|
|
501
|
+
// because when the first brush point was set the direction is undefined.
|
|
502
|
+
const pl = [prevPoint[0] + nx, prevPoint[1] + ny];
|
|
503
|
+
const pr = [prevPoint[0] - nx, prevPoint[1] - ny];
|
|
504
|
+
|
|
505
|
+
lassoPos.push(pl, pr);
|
|
506
|
+
lassoPosFlat.push(pl[0], pl[1], pr[0], pr[1]);
|
|
507
|
+
lassoBrushNormals.push([nx, ny]);
|
|
508
|
+
} else {
|
|
509
|
+
// In this case, we have to adjust the previous normal to create a proper
|
|
510
|
+
// line join by taking the middle between the current and previous normal.
|
|
511
|
+
// const prevPrevPoint = lassoBrushCenterPos.at(-2);
|
|
512
|
+
[nx, ny] = getBrushNormal(point, prevPoint, width);
|
|
513
|
+
|
|
514
|
+
const nextRawBrushNormals = [...lassoBrushNormals, [nx, ny]];
|
|
515
|
+
|
|
516
|
+
// However, to avoid jittery lines we're smoothing the normal
|
|
517
|
+
[nx, ny] = exponentialMovingAverage(nextRawBrushNormals, 1, 10);
|
|
518
|
+
|
|
519
|
+
const [pnx, pny] = lassoBrushNormals.at(-1);
|
|
520
|
+
|
|
521
|
+
const pnx2 = (nx + pnx) / 2;
|
|
522
|
+
const pny2 = (ny + pny) / 2;
|
|
523
|
+
|
|
524
|
+
const pl = [prevPoint[0] + pnx2, prevPoint[1] + pny2];
|
|
525
|
+
const pr = [prevPoint[0] - pnx2, prevPoint[1] - pny2];
|
|
526
|
+
|
|
527
|
+
// We're going to replace the previous left and right points
|
|
528
|
+
lassoPos.splice(N - 1, 2, pl, pr);
|
|
529
|
+
lassoPosFlat.splice(2 * (N - 1), 4, pl[0], pl[1], pr[0], pr[1]);
|
|
530
|
+
lassoBrushNormals.splice(N, 1, [pnx2, pny2]);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const pl = [point[0] + nx, point[1] + ny];
|
|
534
|
+
const pr = [point[0] - nx, point[1] - ny];
|
|
535
|
+
|
|
536
|
+
lassoPos.splice(N, 0, pl, pr);
|
|
537
|
+
lassoPosFlat.splice(2 * N, 0, pl[0], pl[1], pr[0], pr[1]);
|
|
538
|
+
|
|
539
|
+
lassoBrushCenterPos.push(point);
|
|
540
|
+
lassoBrushNormals.push([nx, ny]);
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
let extendLasso = extendFreeform;
|
|
544
|
+
let startLasso = extendFreeform;
|
|
545
|
+
|
|
546
|
+
const extend = (currMousePos) => {
|
|
547
|
+
if (prevMousePos) {
|
|
548
|
+
const d = l2PointDist(
|
|
549
|
+
currMousePos[0],
|
|
550
|
+
currMousePos[1],
|
|
551
|
+
prevMousePos[0],
|
|
552
|
+
prevMousePos[1],
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
if (d > minDist) {
|
|
556
|
+
prevMousePos = currMousePos;
|
|
557
|
+
|
|
558
|
+
extendLasso(pointNorm(currMousePos));
|
|
559
|
+
|
|
560
|
+
if (lassoPos.length > 1) {
|
|
561
|
+
draw();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
if (!isLasso) {
|
|
566
|
+
isLasso = true;
|
|
567
|
+
onStart();
|
|
568
|
+
}
|
|
569
|
+
prevMousePos = currMousePos;
|
|
570
|
+
const point = pointNorm(currMousePos);
|
|
571
|
+
startLasso(point);
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const extendDb = throttleAndDebounce(extend, minDelay, minDelay);
|
|
576
|
+
|
|
577
|
+
const extendPublic = (event, debounced) => {
|
|
578
|
+
const mousePosition = getMousePosition(event);
|
|
579
|
+
if (debounced) {
|
|
580
|
+
return extendDb(mousePosition);
|
|
581
|
+
}
|
|
582
|
+
return extend(mousePosition);
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
const clear = () => {
|
|
586
|
+
lassoPos = [];
|
|
587
|
+
lassoPosFlat = [];
|
|
588
|
+
lassoBrushCenterPos = [];
|
|
589
|
+
lassoBrushNormals = [];
|
|
590
|
+
prevMousePos = undefined;
|
|
591
|
+
draw();
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
const initiatorClickHandler = (event) => {
|
|
595
|
+
showInitiator(event);
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
const initiatorMouseDownHandler = () => {
|
|
599
|
+
isMouseDown = true;
|
|
600
|
+
isLasso = true;
|
|
601
|
+
clear();
|
|
602
|
+
onStart();
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
const initiatorMouseLeaveHandler = () => {
|
|
606
|
+
hideInitiator();
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const end = ({ intersect = false, merge = false, remove = false } = {}) => {
|
|
610
|
+
isLasso = false;
|
|
611
|
+
|
|
612
|
+
const currLassoPos = [...lassoPos];
|
|
613
|
+
const currLassoPosFlat = [...lassoPosFlat];
|
|
614
|
+
|
|
615
|
+
extendDb.cancel();
|
|
616
|
+
|
|
617
|
+
clear();
|
|
618
|
+
|
|
619
|
+
// When `currLassoPos` is empty the user didn't actually lasso
|
|
620
|
+
if (currLassoPos.length > 0) {
|
|
621
|
+
onEnd(currLassoPos, currLassoPosFlat, { intersect, merge, remove });
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
return currLassoPos;
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
const setExtendLasso = (newType) => {
|
|
628
|
+
switch (newType) {
|
|
629
|
+
case 'rectangle': {
|
|
630
|
+
type = newType;
|
|
631
|
+
extendLasso = extendRectangle;
|
|
632
|
+
// This is on purpose. The start of a rectangle & freeform are the same
|
|
633
|
+
startLasso = extendFreeform;
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
case 'brush': {
|
|
638
|
+
type = newType;
|
|
639
|
+
extendLasso = extendBrush;
|
|
640
|
+
startLasso = startBrush;
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
default: {
|
|
645
|
+
type = 'freeform';
|
|
646
|
+
extendLasso = extendFreeform;
|
|
647
|
+
startLasso = extendFreeform;
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
const get = (property) => {
|
|
654
|
+
if (property === 'onDraw') {
|
|
655
|
+
return onDraw;
|
|
656
|
+
}
|
|
657
|
+
if (property === 'onStart') {
|
|
658
|
+
return onStart;
|
|
659
|
+
}
|
|
660
|
+
if (property === 'onEnd') {
|
|
661
|
+
return onEnd;
|
|
662
|
+
}
|
|
663
|
+
if (property === 'enableInitiator') {
|
|
664
|
+
return enableInitiator;
|
|
665
|
+
}
|
|
666
|
+
if (property === 'minDelay') {
|
|
667
|
+
return minDelay;
|
|
668
|
+
}
|
|
669
|
+
if (property === 'minDist') {
|
|
670
|
+
return minDist;
|
|
671
|
+
}
|
|
672
|
+
if (property === 'pointNorm') {
|
|
673
|
+
return pointNorm;
|
|
674
|
+
}
|
|
675
|
+
if (property === 'type') {
|
|
676
|
+
return type;
|
|
677
|
+
}
|
|
678
|
+
if (property === 'brushSize') {
|
|
679
|
+
return brushSize;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
const set = ({
|
|
684
|
+
onDraw: newOnDraw = null,
|
|
685
|
+
onStart: newOnStart = null,
|
|
686
|
+
onEnd: newOnEnd = null,
|
|
687
|
+
enableInitiator: newEnableInitiator = null,
|
|
688
|
+
initiatorParentElement: newInitiatorParentElement = null,
|
|
689
|
+
longPressIndicatorParentElement: newLongPressIndicatorParentElement = null,
|
|
690
|
+
minDelay: newMinDelay = null,
|
|
691
|
+
minDist: newMinDist = null,
|
|
692
|
+
pointNorm: newPointNorm = null,
|
|
693
|
+
type: newType = null,
|
|
694
|
+
brushSize: newBrushSize = null,
|
|
695
|
+
} = {}) => {
|
|
696
|
+
onDraw = ifNotNull(newOnDraw, onDraw);
|
|
697
|
+
onStart = ifNotNull(newOnStart, onStart);
|
|
698
|
+
onEnd = ifNotNull(newOnEnd, onEnd);
|
|
699
|
+
enableInitiator = ifNotNull(newEnableInitiator, enableInitiator);
|
|
700
|
+
minDelay = ifNotNull(newMinDelay, minDelay);
|
|
701
|
+
minDist = ifNotNull(newMinDist, minDist);
|
|
702
|
+
pointNorm = ifNotNull(newPointNorm, pointNorm);
|
|
703
|
+
brushSize = ifNotNull(newBrushSize, brushSize);
|
|
704
|
+
|
|
705
|
+
if (
|
|
706
|
+
newInitiatorParentElement !== null &&
|
|
707
|
+
newInitiatorParentElement !== initiatorParentElement
|
|
708
|
+
) {
|
|
709
|
+
initiatorParentElement.removeChild(initiator);
|
|
710
|
+
newInitiatorParentElement.appendChild(initiator);
|
|
711
|
+
initiatorParentElement = newInitiatorParentElement;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (
|
|
715
|
+
newLongPressIndicatorParentElement !== null &&
|
|
716
|
+
newLongPressIndicatorParentElement !== longPressIndicatorParentElement
|
|
717
|
+
) {
|
|
718
|
+
longPressIndicatorParentElement.removeChild(longPress);
|
|
719
|
+
newLongPressIndicatorParentElement.appendChild(longPress);
|
|
720
|
+
longPressIndicatorParentElement = newLongPressIndicatorParentElement;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (enableInitiator) {
|
|
724
|
+
initiator.addEventListener('click', initiatorClickHandler);
|
|
725
|
+
initiator.addEventListener('mousedown', initiatorMouseDownHandler);
|
|
726
|
+
initiator.addEventListener('mouseleave', initiatorMouseLeaveHandler);
|
|
727
|
+
} else {
|
|
728
|
+
initiator.removeEventListener('mousedown', initiatorMouseDownHandler);
|
|
729
|
+
initiator.removeEventListener('mouseleave', initiatorMouseLeaveHandler);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (newType !== null) {
|
|
733
|
+
setExtendLasso(newType);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
const destroy = () => {
|
|
738
|
+
initiatorParentElement.removeChild(initiator);
|
|
739
|
+
longPressIndicatorParentElement.removeChild(longPress);
|
|
740
|
+
window.removeEventListener('mouseup', mouseUpHandler);
|
|
741
|
+
initiator.removeEventListener('click', initiatorClickHandler);
|
|
742
|
+
initiator.removeEventListener('mousedown', initiatorMouseDownHandler);
|
|
743
|
+
initiator.removeEventListener('mouseleave', initiatorMouseLeaveHandler);
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
const withPublicMethods = () => (self) =>
|
|
747
|
+
assign(self, {
|
|
748
|
+
clear,
|
|
749
|
+
destroy,
|
|
750
|
+
end,
|
|
751
|
+
extend: extendPublic,
|
|
752
|
+
get,
|
|
753
|
+
set,
|
|
754
|
+
showInitiator,
|
|
755
|
+
hideInitiator,
|
|
756
|
+
showLongPressIndicator,
|
|
757
|
+
hideLongPressIndicator,
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
initiatorParentElement.appendChild(initiator);
|
|
761
|
+
longPressIndicatorParentElement.appendChild(longPress);
|
|
762
|
+
|
|
763
|
+
set({
|
|
764
|
+
onDraw,
|
|
765
|
+
onStart,
|
|
766
|
+
onEnd,
|
|
767
|
+
enableInitiator,
|
|
768
|
+
initiatorParentElement,
|
|
769
|
+
type,
|
|
770
|
+
brushSize,
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
return pipe(
|
|
774
|
+
withStaticProperty('initiator', initiator),
|
|
775
|
+
withStaticProperty('longPressIndicator', longPress),
|
|
776
|
+
withPublicMethods(),
|
|
777
|
+
withConstructor(createLasso),
|
|
778
|
+
)({});
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
export default createLasso;
|