Skip to content

Commit 913570e

Browse files
committed
Fix a lasso selection issue and improve lasso long press style
1 parent 50007d1 commit 913570e

8 files changed

Lines changed: 115 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## v1.6.1
2+
3+
- Update third party libraries
4+
- Improve lasso long press indicator styling
5+
- Fix an issue where a lasso with less than three control points
6+
17
## v1.6.0
28

39
- Add the ability to filter down points via `scatterplot.filter(pointIdxs)`. This can be useful if you need to temporarily need to hide points without having to re-instantiate the regl-scatterplot instance. E.g., when calling `scatterplot.filter([0, 1, 2])`, only the first, second, and third point will remain visible. All other points (and their related point connections) will be visually and interactively hidden.

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@
2929
"prepublishOnly": "npm run test; rm -rf dist/*; npm run build;",
3030
"prerelease": "rm -rf dist/*; npm run build; zip -r dist.zip dist",
3131
"pretest": "npm run lint",
32-
"start": "vite",
32+
"start": "vite --port=3000",
3333
"test": "rollup -c ./rollup.test.config.mjs | tape-run --render='tap-spec'",
3434
"watch": "rollup -cw"
3535
},
3636
"dependencies": {
37-
"@flekschas/utils": "^0.29.0",
37+
"@flekschas/utils": "^0.30.1",
3838
"dom-2d-camera": "~2.2.5",
3939
"gl-matrix": "~3.4.3",
4040
"kdbush": "~3.0.0",

src/index.js

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import createPubSub from 'pub-sub-es';
44
import { mat4, vec4 } from 'gl-matrix';
55
import createLine from 'regl-line';
66
import {
7+
hasSameElements,
78
identity,
89
rangeMap,
910
unionIntegers,
@@ -117,6 +118,7 @@ import {
117118
createTextureFromUrl,
118119
dist,
119120
getBBox,
121+
isValidBBox,
120122
isConditionalArray,
121123
isPositiveNumber,
122124
isStrictlyPositiveNumber,
@@ -570,6 +572,9 @@ const createScatterplot = (
570572
const findPointsInLasso = (lassoPolygon) => {
571573
// get the bounding box of the lasso selection...
572574
const bBox = getBBox(lassoPolygon);
575+
576+
if (!isValidBBox(bBox)) return [];
577+
573578
// ...to efficiently preselect potentially selected points
574579
const pointsInBBox = getPointsInBBox(...bBox);
575580
// next we test each point in the bounding box if it is in the polygon too
@@ -675,14 +680,28 @@ const createScatterplot = (
675680
*/
676681
const select = (pointIdxs, { merge = false, preventEvent = false } = {}) => {
677682
const pointIdxsArr = Array.isArray(pointIdxs) ? pointIdxs : [pointIdxs];
683+
const currSelectedPoints = [...selectedPoints];
678684

679685
if (merge) {
680686
selectedPoints = unionIntegers(selectedPoints, pointIdxsArr);
687+
if (currSelectedPoints.length === selectedPoints.length) {
688+
draw = true;
689+
return;
690+
}
681691
} else {
682692
// Unset previously highlight point connections
683693
if (selectedPoints && selectedPoints.length)
684694
setPointConnectionColorState(selectedPoints, 0);
685695
selectedPoints = pointIdxsArr;
696+
if (currSelectedPoints.length > 0 && selectedPoints.length === 0) {
697+
deselect({ preventEvent });
698+
return;
699+
}
700+
}
701+
702+
if (hasSameElements(currSelectedPoints, selectedPoints)) {
703+
draw = true;
704+
return;
686705
}
687706

688707
const selectedPointsBuffer = [];
@@ -935,7 +954,7 @@ const createScatterplot = (
935954
const mouseMoveHandler = (event) => {
936955
if (!isInit || (!isMouseInCanvas && !mouseDown)) return;
937956

938-
getRelativeMousePosition(event);
957+
const currentMousePosition = getRelativeMousePosition(event);
939958

940959
// Only ray cast if the mouse cursor is inside
941960
if (isMouseInCanvas && !lassoActive) {
@@ -945,10 +964,13 @@ const createScatterplot = (
945964
if (lassoActive) {
946965
event.preventDefault();
947966
lassoManager.extend(event, true);
948-
} else if (lassoOnLongPress) {
949-
lassoManager.hideLongPressIndicator({
950-
time: lassoLongPressRevertEffectTime,
951-
});
967+
} else {
968+
const mouseMoveDist = dist(...currentMousePosition, ...mouseDownPosition);
969+
if (mouseDown && lassoOnLongPress && mouseMoveDist >= lassoMinDist) {
970+
lassoManager.hideLongPressIndicator({
971+
time: lassoLongPressRevertEffectTime,
972+
});
973+
}
952974
}
953975

954976
if (mouseDownTimeout >= 0) {
@@ -2447,10 +2469,7 @@ const createScatterplot = (
24472469
};
24482470

24492471
const updateLassoLongPressIndicatorStyle = () => {
2450-
const v =
2451-
Math.round(backgroundColorBrightness) > 0.5
2452-
? Math.round(backgroundColorBrightness * 255) - 85
2453-
: Math.round(backgroundColorBrightness * 255) + 85;
2472+
const v = Math.round(backgroundColorBrightness) > 0.5 ? 0 : 255;
24542473

24552474
lassoManager.longPressIndicator.style.color = `rgb(${v}, ${v}, ${v})`;
24562475
lassoManager.longPressIndicator.dataset.color = `rgb(${v}, ${v}, ${v})`;

src/lasso-manager/create-long-press-animations.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,17 @@ const getCircleInAnimation = (t, d) =>
2424

2525
const getMainIn = (mainEffectPercent, currentColor, targetColor) => `
2626
@keyframes mainIn {
27+
0% {
28+
color: ${currentColor};
29+
opacity: 0;
30+
}
2731
0%, ${mainEffectPercent}% {
2832
color: ${currentColor};
33+
opacity: 1;
2934
}
3035
100% {
3136
color: ${targetColor};
37+
opacity: 0.8;
3238
}
3339
}
3440
`;

src/lasso-manager/create-long-press-elements.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ export const createLongPressElements = () => {
55
Math.random().toString(36).substring(2, 5);
66
longPress.id = `lasso-long-press-${longPressId}`;
77
longPress.style.position = 'fixed';
8-
longPress.style.width = '1.5rem';
9-
longPress.style.height = '1.5rem';
8+
longPress.style.width = '1.25rem';
9+
longPress.style.height = '1.25rem';
1010
longPress.style.pointerEvents = 'none';
1111
longPress.style.transform = 'translate(-50%,-50%)';
1212

1313
const longPressCircle = document.createElement('div');
1414
longPressCircle.style.position = 'absolute';
1515
longPressCircle.style.top = 0;
1616
longPressCircle.style.left = 0;
17-
longPressCircle.style.width = '1.5rem';
18-
longPressCircle.style.height = '1.5rem';
17+
longPressCircle.style.width = '1.25rem';
18+
longPressCircle.style.height = '1.25rem';
1919
longPressCircle.style.clipPath = 'inset(0px 0px 0px 50%)';
2020
longPressCircle.style.opacity = 0;
2121
longPress.appendChild(longPressCircle);
@@ -24,10 +24,10 @@ export const createLongPressElements = () => {
2424
longPressCircleLeft.style.position = 'absolute';
2525
longPressCircleLeft.style.top = 0;
2626
longPressCircleLeft.style.left = 0;
27-
longPressCircleLeft.style.width = '1rem';
28-
longPressCircleLeft.style.height = '1rem';
29-
longPressCircleLeft.style.border = '0.25rem solid currentcolor';
30-
longPressCircleLeft.style.borderRadius = '1rem';
27+
longPressCircleLeft.style.width = '0.8rem';
28+
longPressCircleLeft.style.height = '0.8rem';
29+
longPressCircleLeft.style.border = '0.2rem solid currentcolor';
30+
longPressCircleLeft.style.borderRadius = '0.8rem';
3131
longPressCircleLeft.style.clipPath = 'inset(0px 50% 0px 0px)';
3232
longPressCircleLeft.style.transform = 'rotate(0deg)';
3333
longPressCircle.appendChild(longPressCircleLeft);
@@ -36,10 +36,10 @@ export const createLongPressElements = () => {
3636
longPressCircleRight.style.position = 'absolute';
3737
longPressCircleRight.style.top = 0;
3838
longPressCircleRight.style.left = 0;
39-
longPressCircleRight.style.width = '1rem';
40-
longPressCircleRight.style.height = '1rem';
41-
longPressCircleRight.style.border = '0.25rem solid currentcolor';
42-
longPressCircleRight.style.borderRadius = '1rem';
39+
longPressCircleRight.style.width = '0.8rem';
40+
longPressCircleRight.style.height = '0.8rem';
41+
longPressCircleRight.style.border = '0.2rem solid currentcolor';
42+
longPressCircleRight.style.borderRadius = '0.8rem';
4343
longPressCircleRight.style.clipPath = 'inset(0px 50% 0px 0px)';
4444
longPressCircleRight.style.transform = 'rotate(0deg)';
4545
longPressCircle.appendChild(longPressCircleRight);
@@ -48,9 +48,9 @@ export const createLongPressElements = () => {
4848
longPressEffect.style.position = 'absolute';
4949
longPressEffect.style.top = 0;
5050
longPressEffect.style.left = 0;
51-
longPressEffect.style.width = '1.5rem';
52-
longPressEffect.style.height = '1.5rem';
53-
longPressEffect.style.borderRadius = '1.5rem';
51+
longPressEffect.style.width = '1.25rem';
52+
longPressEffect.style.height = '1.25rem';
53+
longPressEffect.style.borderRadius = '1.25rem';
5454
longPressEffect.style.background = 'currentcolor';
5555
longPressEffect.style.transform = 'scale(0)';
5656
longPressEffect.style.opacity = 0;

src/utils.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,19 @@ export const getBBox = (positions2d) => {
9595
return [xMin, yMin, xMax, yMax];
9696
};
9797

98+
/**
99+
* Test whether a bounding box is actually specifying an area
100+
* @param {array} bBox The bounding box to be checked
101+
* @return {array} `true` if the bounding box is valid
102+
*/
103+
export const isValidBBox = ([xMin, yMin, xMax, yMax]) =>
104+
Number.isFinite(xMin) &&
105+
Number.isFinite(yMin) &&
106+
Number.isFinite(xMax) &&
107+
Number.isFinite(yMax) &&
108+
xMax - xMin > 0 &&
109+
yMax - yMin > 0;
110+
98111
/**
99112
* Convert a HEX-encoded color to an RGB-encoded color
100113
* @param {string} hex HEX-encoded color string.

tests/index.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
IMAGE_LOAD_ERROR,
4747
} from '../src/constants';
4848

49-
import { toRgba, isNormFloatArray } from '../src/utils';
49+
import { toRgba, isNormFloatArray, isValidBBox } from '../src/utils';
5050

5151
import {
5252
asyncForEach,
@@ -1983,6 +1983,7 @@ test(
19831983
'should have silently selected three points'
19841984
);
19851985

1986+
scatterplot.deselect();
19861987
scatterplot.select([0, 2, 4]);
19871988
scatterplot.deselect({ preventEvent: true });
19881989

@@ -2365,6 +2366,42 @@ test(
23652366

23662367
/* --------------------------------- Utils ---------------------------------- */
23672368

2369+
test(
2370+
'isValidBBox()',
2371+
catchError(async (t) => {
2372+
t.equal(
2373+
isValidBBox([1, 2, 3, 4]),
2374+
true,
2375+
'should recognize valid bounding box'
2376+
);
2377+
t.equal(
2378+
isValidBBox([1, 2, 1, 4]),
2379+
false,
2380+
'should recognize invalid bounding box (x range is zero)'
2381+
);
2382+
t.equal(
2383+
isValidBBox([1, 2, 3, 2]),
2384+
false,
2385+
'should recognize invalid bounding box (y range is zero)'
2386+
);
2387+
t.equal(
2388+
isValidBBox([1, Infinity, 3, 2]),
2389+
false,
2390+
'should recognize invalid bounding box (infinity is not allowed)'
2391+
);
2392+
t.equal(
2393+
isValidBBox([1, -Infinity, 3, 2]),
2394+
false,
2395+
'should recognize invalid bounding box (-infinity is not allowed)'
2396+
);
2397+
t.equal(
2398+
isValidBBox([1, Number.NaN, 3, 2]),
2399+
false,
2400+
'should recognize invalid bounding box (NaN is not allowed)'
2401+
);
2402+
})
2403+
);
2404+
23682405
test(
23692406
'isNormFloatArray()',
23702407
catchError(async (t) => {

0 commit comments

Comments
 (0)