Skip to content

Commit 91aa396

Browse files
committed
Expose selected and filtered point indices
1 parent 1ac6256 commit 91aa396

4 files changed

Lines changed: 73 additions & 50 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
https://user-images.githubusercontent.com/932103/222810324-3e048176-fd1d-4ede-a836-511c548f09ff.mp4
88

9+
- Add the ability to retrieve selected and filtered point indices via `scatterplot.get('selectedPoints')` and `scatterplot.get('filteredPoints')` respectively.
10+
911
## v1.5.1
1012

1113
- Refactor lasso manager to support SSR ([#101](https://github.com/flekschas/regl-scatterplot/issues/101))

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,9 @@ can be read and written via [`scatterplot.get()`](#scatterplot.get) and [`scatte
607607
| opacity | float | `1` | Must be in ]0, 1] | `true` | `false` |
608608
| opacityInactiveMax | float | `1` | Must be in [0, 1] | `true` | `false` |
609609
| opacityInactiveScale | float | `1` | Must be in [0, 1] | `true` | `false` |
610-
| points | tuple | `[0.5, 2.3]` | | `false` | `false` |
610+
| points | tuple[] | `[[0.5, 2.3], ...]` | | `false` | `false` |
611+
| selectedPoints | int[] | `[4, 2]` | | `false` | `false` |
612+
| filteredPoints | int[] | `[4, 2]` | | `false` | `false` |
611613
| pointsInView | int[] | `[1, 2, 12]` | | `false` | `false` |
612614
| pointColor | quadruple | `[0.66, 0.66, 0.66, 1]` | single value or list of hex, rgb, rgba | `true` | `false` |
613615
| pointColorActive | quadruple | `[0, 0.55, 1, 1]` | single value or list of hex, rgb, rgba | `true` | `false` |

src/index.js

Lines changed: 56 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,11 @@ const createScatterplot = (
280280
let mouseDownTime = null;
281281
let mouseDownPosition = [0, 0];
282282
let mouseDownTimeout = -1;
283-
let selection = [];
284-
const selectionSet = new Set();
285-
const selectionConnecionSet = new Set();
283+
let selectedPoints = [];
284+
const selectedPointsSet = new Set();
285+
const selectedPointsConnectionSet = new Set();
286286
let filteredPoints = [];
287-
const filteredPointSet = new Set();
287+
const filteredPointsSet = new Set();
288288
let numPoints = 0;
289289
let numPointsInView = 0;
290290
let lassoActive = false;
@@ -518,15 +518,15 @@ const createScatterplot = (
518518
};
519519

520520
const getPoints = () => {
521-
if (filteredPointSet.size > 0)
522-
return searchIndex.points.filter((_, i) => filteredPointSet.has(i));
521+
if (filteredPointsSet.size > 0)
522+
return searchIndex.points.filter((_, i) => filteredPointsSet.has(i));
523523
return searchIndex.points;
524524
};
525525

526526
const getPointsInBBox = (x0, y0, x1, y1) => {
527527
const pointsInBBox = searchIndex.range(x0, y0, x1, y1);
528-
if (filteredPointSet.size > 0)
529-
return pointsInBBox.filter((i) => filteredPointSet.has(i));
528+
if (filteredPointsSet.size > 0)
529+
return pointsInBBox.filter((i) => filteredPointsSet.has(i));
530530
return pointsInBBox;
531531
};
532532

@@ -600,7 +600,7 @@ const createScatterplot = (
600600
const isNormal = stateIndex === 0;
601601
const lineIdCacher =
602602
stateIndex === 1
603-
? (lineId) => selectionConnecionSet.add(lineId)
603+
? (lineId) => selectedPointsConnectionSet.add(lineId)
604604
: identity;
605605

606606
// Get line IDs
@@ -619,7 +619,7 @@ const createScatterplot = (
619619
const buffer = pointConnections.getData().opacities;
620620

621621
lineIds
622-
.filter((lineId) => !selectionConnecionSet.has(+lineId))
622+
.filter((lineId) => !selectedPointsConnectionSet.has(+lineId))
623623
.forEach((lineId) => {
624624
const index = pointConnectionMap[lineId][0];
625625
const numPointPerLine = pointConnectionMap[lineId][2];
@@ -654,16 +654,16 @@ const createScatterplot = (
654654
];
655655

656656
const isPointFilteredOut = (pointIdx) =>
657-
filteredPointSet.size > 0 && !filteredPointSet.has(pointIdx);
657+
filteredPointsSet.size > 0 && !filteredPointsSet.has(pointIdx);
658658

659659
const deselect = ({ preventEvent = false } = {}) => {
660660
if (lassoClearEvent === LASSO_CLEAR_ON_DESELECT) lassoClear();
661-
if (selection.length) {
661+
if (selectedPoints.length) {
662662
if (!preventEvent) pubSub.publish('deselect');
663-
selectionConnecionSet.clear();
664-
setPointConnectionColorState(selection, 0);
665-
selection = [];
666-
selectionSet.clear();
663+
selectedPointsConnectionSet.clear();
664+
setPointConnectionColorState(selectedPoints, 0);
665+
selectedPoints = [];
666+
selectedPointsSet.clear();
667667
draw = true;
668668
}
669669
};
@@ -677,48 +677,48 @@ const createScatterplot = (
677677
const pointIdxsArr = Array.isArray(pointIdxs) ? pointIdxs : [pointIdxs];
678678

679679
if (merge) {
680-
selection = unionIntegers(selection, pointIdxsArr);
680+
selectedPoints = unionIntegers(selectedPoints, pointIdxsArr);
681681
} else {
682682
// Unset previously highlight point connections
683-
if (selection && selection.length)
684-
setPointConnectionColorState(selection, 0);
685-
selection = pointIdxsArr;
683+
if (selectedPoints && selectedPoints.length)
684+
setPointConnectionColorState(selectedPoints, 0);
685+
selectedPoints = pointIdxsArr;
686686
}
687687

688-
const selectionBuffer = [];
688+
const selectedPointsBuffer = [];
689689

690-
selectionSet.clear();
691-
selectionConnecionSet.clear();
690+
selectedPointsSet.clear();
691+
selectedPointsConnectionSet.clear();
692692

693-
for (let i = selection.length - 1; i >= 0; i--) {
694-
const pointIdx = selection[i];
693+
for (let i = selectedPoints.length - 1; i >= 0; i--) {
694+
const pointIdx = selectedPoints[i];
695695

696696
if (
697697
pointIdx < 0 ||
698698
pointIdx >= numPoints ||
699699
isPointFilteredOut(pointIdx)
700700
) {
701-
// Remove invalid selection
702-
selection.splice(i, 1);
701+
// Remove invalid selected points
702+
selectedPoints.splice(i, 1);
703703
continue;
704704
}
705705

706-
selectionSet.add(pointIdx);
707-
selectionBuffer.push.apply(
708-
selectionBuffer,
706+
selectedPointsSet.add(pointIdx);
707+
selectedPointsBuffer.push.apply(
708+
selectedPointsBuffer,
709709
indexToStateTexCoord(pointIdx)
710710
);
711711
}
712712

713713
selectedPointsIndexBuffer({
714714
usage: 'dynamic',
715715
type: 'float',
716-
data: selectionBuffer,
716+
data: selectedPointsBuffer,
717717
});
718718

719-
setPointConnectionColorState(selection, 1);
719+
setPointConnectionColorState(selectedPoints, 1);
720720

721-
if (!preventEvent) pubSub.publish('select', { points: selection });
721+
if (!preventEvent) pubSub.publish('select', { points: selectedPoints });
722722

723723
draw = true;
724724
};
@@ -740,19 +740,20 @@ const createScatterplot = (
740740
if (
741741
+oldHoveredPoint >= 0 &&
742742
newHoveredPoint &&
743-
!selectionSet.has(oldHoveredPoint)
743+
!selectedPointsSet.has(oldHoveredPoint)
744744
) {
745745
setPointConnectionColorState([oldHoveredPoint], 0);
746746
}
747747
hoveredPoint = point;
748748
hoveredPointIndexBuffer.subdata(indexToStateTexCoord(point));
749-
if (!selectionSet.has(point)) setPointConnectionColorState([point], 2);
749+
if (!selectedPointsSet.has(point))
750+
setPointConnectionColorState([point], 2);
750751
if (newHoveredPoint && !preventEvent)
751752
pubSub.publish('pointover', hoveredPoint);
752753
} else {
753754
needsRedraw = +hoveredPoint >= 0;
754755
if (needsRedraw) {
755-
if (!selectionSet.has(hoveredPoint)) {
756+
if (!selectedPointsSet.has(hoveredPoint)) {
756757
setPointConnectionColorState([hoveredPoint], 0);
757758
}
758759
if (!preventEvent) {
@@ -898,9 +899,12 @@ const createScatterplot = (
898899
// initiator if the use click into the void
899900
const clostestPoint = raycast();
900901
if (clostestPoint >= 0) {
901-
if (selection.length && lassoClearEvent === LASSO_CLEAR_ON_DESELECT) {
902+
if (
903+
selectedPoints.length &&
904+
lassoClearEvent === LASSO_CLEAR_ON_DESELECT
905+
) {
902906
// Special case where we silently "deselect" the previous points by
903-
// overriding the selection. Hence, we need to clear the lasso.
907+
// overriding the selected points. Hence, we need to clear the lasso.
904908
lassoClear();
905909
}
906910
select([clostestPoint], {
@@ -959,7 +963,7 @@ const createScatterplot = (
959963
const blurHandler = () => {
960964
if (!isInit) return;
961965

962-
if (+hoveredPoint >= 0 && !selectionSet.has(hoveredPoint))
966+
if (+hoveredPoint >= 0 && !selectedPointsSet.has(hoveredPoint))
963967
setPointConnectionColorState([hoveredPoint], 0);
964968
hoveredPoint = undefined;
965969
isMouseInCanvas = false;
@@ -1332,7 +1336,7 @@ const createScatterplot = (
13321336
};
13331337
const getNormalNumPoints = () =>
13341338
filteredPoints && filteredPoints.length ? filteredPoints.length : numPoints;
1335-
const getSelectedNumPoints = () => selection.length;
1339+
const getSelectedNumPoints = () => selectedPoints.length;
13361340
const getPointOpacityMaxBase = () =>
13371341
getSelectedNumPoints() > 0 ? opacityInactiveMax : 1;
13381342
const getPointOpacityScaleBase = () =>
@@ -1951,7 +1955,7 @@ const createScatterplot = (
19511955
*/
19521956
const unfilter = ({ preventEvent = false } = {}) => {
19531957
filteredPoints = [];
1954-
filteredPointSet.clear();
1958+
filteredPointsSet.clear();
19551959
normalPointsIndexBuffer.subdata(createPointIndex(numPoints));
19561960

19571961
return new Promise((resolve) => {
@@ -1989,10 +1993,10 @@ const createScatterplot = (
19891993

19901994
if (filteredPoints.length === 0) return unfilter({ preventEvent });
19911995

1992-
filteredPointSet.clear();
1996+
filteredPointsSet.clear();
19931997

19941998
const filteredPointsBuffer = [];
1995-
const filteredSelection = [];
1999+
const filteredSelectedPoints = [];
19962000

19972001
for (let i = filteredPoints.length - 1; i >= 0; i--) {
19982002
const pointIdx = filteredPoints[i];
@@ -2003,20 +2007,21 @@ const createScatterplot = (
20032007
continue;
20042008
}
20052009

2006-
filteredPointSet.add(pointIdx);
2010+
filteredPointsSet.add(pointIdx);
20072011
filteredPointsBuffer.push.apply(
20082012
filteredPointsBuffer,
20092013
indexToStateTexCoord(pointIdx)
20102014
);
20112015

2012-
if (selectionSet.has(pointIdx)) filteredSelection.push(pointIdx);
2016+
if (selectedPointsSet.has(pointIdx))
2017+
filteredSelectedPoints.push(pointIdx);
20132018
}
20142019

20152020
// Update the normal points index buffers
20162021
normalPointsIndexBuffer.subdata(filteredPointsBuffer);
20172022

20182023
// Update selection
2019-
select(filteredSelection, { preventEvent });
2024+
select(filteredSelectedPoints, { preventEvent });
20202025

20212026
// Unset any potentially hovered point
20222027
hover(-1, { preventEvent });
@@ -2041,7 +2046,7 @@ const createScatterplot = (
20412046
if (!preventEvent) pubSub.publish('pointConnectionsDraw');
20422047
// We have to re-apply the selection because the connections might
20432048
// have changed
2044-
select(filteredSelection, { preventEvent });
2049+
select(filteredSelectedPoints, { preventEvent });
20452050
finish();
20462051
});
20472052
} else {
@@ -2236,7 +2241,7 @@ const createScatterplot = (
22362241

22372242
// Reset filter
22382243
filteredPoints = [];
2239-
filteredPointSet.clear();
2244+
filteredPointsSet.clear();
22402245

22412246
let pointsCached = false;
22422247
if (points) {
@@ -2819,6 +2824,8 @@ const createScatterplot = (
28192824
if (property === 'opacityInactiveMax') return opacityInactiveMax;
28202825
if (property === 'opacityInactiveScale') return opacityInactiveScale;
28212826
if (property === 'points') return searchIndex.points;
2827+
if (property === 'selectedPoints') return [...selectedPoints];
2828+
if (property === 'filteredPoints') return [...filteredPoints];
28222829
if (property === 'pointsInView') return getPointsInView();
28232830
if (property === 'pointColor')
28242831
return pointColor.length === 1 ? pointColor[0] : pointColor;
@@ -3360,7 +3367,7 @@ const createScatterplot = (
33603367
drawPointBodies();
33613368
if (!mouseDown && (showReticle || drawReticleOnce)) drawReticle();
33623369
if (hoveredPoint >= 0) drawHoveredPoint();
3363-
if (selection.length) drawSelectedPoints();
3370+
if (selectedPoints.length) drawSelectedPoints();
33643371

33653372
lasso.draw({
33663373
projection: getProjection(),

tests/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,6 +1961,12 @@ test(
19611961
'should have selected point 0, 2, and 4'
19621962
);
19631963

1964+
t.deepEqual(
1965+
scatterplot.get('selectedPoints'),
1966+
[0, 2, 4],
1967+
'should be able to retrieve the selected points'
1968+
);
1969+
19641970
scatterplot.deselect();
19651971

19661972
await wait(0);
@@ -2127,6 +2133,12 @@ test(
21272133
'should have two points (1 and 3) in view'
21282134
);
21292135

2136+
t.deepEqual(
2137+
scatterplot.get('filteredPoints'),
2138+
[1, 3],
2139+
'should be able to retrieve the filtered points'
2140+
);
2141+
21302142
let selectedPoints = [];
21312143
const selectHandler = ({ points: newSelectedPoints }) => {
21322144
selectedPoints = [...newSelectedPoints];

0 commit comments

Comments
 (0)