@@ -283,6 +283,8 @@ const createScatterplot = (
283283 let selection = [ ] ;
284284 const selectionSet = new Set ( ) ;
285285 const selectionConnecionSet = new Set ( ) ;
286+ let filteredPoints = [ ] ;
287+ const filteredPointSet = new Set ( ) ;
286288 let numPoints = 0 ;
287289 let numPointsInView = 0 ;
288290 let lassoActive = false ;
@@ -515,14 +517,27 @@ const createScatterplot = (
515517 return computedPointSizeMouseDetection * pointScale * pxNdc * 0.66 ;
516518 } ;
517519
520+ const getPoints = ( ) => {
521+ if ( filteredPointSet . size > 0 )
522+ return searchIndex . points . filter ( ( _ , i ) => filteredPointSet . has ( i ) ) ;
523+ return searchIndex . points ;
524+ } ;
525+
526+ const getPointsInBBox = ( x0 , y0 , x1 , y1 ) => {
527+ const pointsInBBox = searchIndex . range ( x0 , y0 , x1 , y1 ) ;
528+ if ( filteredPointSet . size > 0 )
529+ return pointsInBBox . filter ( ( i ) => filteredPointSet . has ( i ) ) ;
530+ return pointsInBBox ;
531+ } ;
532+
518533 const raycast = ( ) => {
519534 const [ xGl , yGl ] = getMouseGlPos ( ) ;
520535 const [ xNdc , yNdc ] = getScatterGlPos ( xGl , yGl ) ;
521536
522537 const pointSizeNdc = getPointSizeNdc ( ) ;
523538
524539 // Get all points within a close range
525- const pointsInBBox = searchIndex . range (
540+ const pointsInBBox = getPointsInBBox (
526541 xNdc - pointSizeNdc ,
527542 yNdc - pointSizeNdc ,
528543 xNdc + pointSizeNdc ,
@@ -556,7 +571,7 @@ const createScatterplot = (
556571 // get the bounding box of the lasso selection...
557572 const bBox = getBBox ( lassoPolygon ) ;
558573 // ...to efficiently preselect potentially selected points
559- const pointsInBBox = searchIndex . range ( ...bBox ) ;
574+ const pointsInBBox = getPointsInBBox ( ...bBox ) ;
560575 // next we test each point in the bounding box if it is in the polygon too
561576 const pointsInPolygon = [ ] ;
562577 pointsInBBox . forEach ( ( pointIdx ) => {
@@ -638,6 +653,9 @@ const createScatterplot = (
638653 Math . floor ( index / stateTexRes ) / stateTexRes + stateTexEps ,
639654 ] ;
640655
656+ const isPointFilteredOut = ( pointIdx ) =>
657+ filteredPointSet . size > 0 && ! filteredPointSet . has ( pointIdx ) ;
658+
641659 const deselect = ( { preventEvent = false } = { } ) => {
642660 if ( lassoClearEvent === LASSO_CLEAR_ON_DESELECT ) lassoClear ( ) ;
643661 if ( selection . length ) {
@@ -675,15 +693,21 @@ const createScatterplot = (
675693 for ( let i = selection . length - 1 ; i >= 0 ; i -- ) {
676694 const pointIdx = selection [ i ] ;
677695
678- if ( pointIdx < 0 || pointIdx >= numPoints ) {
696+ if (
697+ pointIdx < 0 ||
698+ pointIdx >= numPoints ||
699+ isPointFilteredOut ( pointIdx )
700+ ) {
679701 // Remove invalid selection
680702 selection . splice ( i , 1 ) ;
681- } else {
682- selectionSet . add ( pointIdx ) ;
683- const texCoords = indexToStateTexCoord ( pointIdx ) ;
684- selectionBuffer . push ( texCoords [ 0 ] ) ;
685- selectionBuffer . push ( texCoords [ 1 ] ) ;
703+ continue ;
686704 }
705+
706+ selectionSet . add ( pointIdx ) ;
707+ selectionBuffer . push . apply (
708+ selectionBuffer ,
709+ indexToStateTexCoord ( pointIdx )
710+ ) ;
687711 }
688712
689713 selectedPointsIndexBuffer ( {
@@ -699,6 +723,51 @@ const createScatterplot = (
699723 draw = true ;
700724 } ;
701725
726+ /**
727+ * @param {number | number[] } point
728+ * @param {import('./types').ScatterplotMethodOptions['hover'] } options
729+ */
730+ const hover = (
731+ point ,
732+ { showReticleOnce = false , preventEvent = false } = { }
733+ ) => {
734+ let needsRedraw = false ;
735+
736+ if ( point >= 0 && point < numPoints ) {
737+ needsRedraw = true ;
738+ const oldHoveredPoint = hoveredPoint ;
739+ const newHoveredPoint = point !== hoveredPoint ;
740+ if (
741+ + oldHoveredPoint >= 0 &&
742+ newHoveredPoint &&
743+ ! selectionSet . has ( oldHoveredPoint )
744+ ) {
745+ setPointConnectionColorState ( [ oldHoveredPoint ] , 0 ) ;
746+ }
747+ hoveredPoint = point ;
748+ hoveredPointIndexBuffer . subdata ( indexToStateTexCoord ( point ) ) ;
749+ if ( ! selectionSet . has ( point ) ) setPointConnectionColorState ( [ point ] , 2 ) ;
750+ if ( newHoveredPoint && ! preventEvent )
751+ pubSub . publish ( 'pointover' , hoveredPoint ) ;
752+ } else {
753+ needsRedraw = + hoveredPoint >= 0 ;
754+ if ( needsRedraw ) {
755+ if ( ! selectionSet . has ( hoveredPoint ) ) {
756+ setPointConnectionColorState ( [ hoveredPoint ] , 0 ) ;
757+ }
758+ if ( ! preventEvent ) {
759+ pubSub . publish ( 'pointout' , hoveredPoint ) ;
760+ }
761+ }
762+ hoveredPoint = undefined ;
763+ }
764+
765+ if ( needsRedraw ) {
766+ draw = true ;
767+ drawReticleOnce = showReticleOnce ;
768+ }
769+ } ;
770+
702771 const getRelativeMousePosition = ( event ) => {
703772 const rect = canvas . getBoundingClientRect ( ) ;
704773
@@ -908,11 +977,21 @@ const createScatterplot = (
908977 rgba [ i * 4 ] = pointSize [ i ] || 0 ;
909978 rgba [ i * 4 + 1 ] = Math . min ( 1 , opacity [ i ] || 0 ) ;
910979
911- const active = Number ( ( pointColorActive [ i ] || pointColorActive [ 0 ] ) [ 3 ] ) ;
912- rgba [ i * 4 + 2 ] = Math . min ( 1 , Number . isNaN ( active ) ? 1 : active ) ;
980+ const activeOpacity = Number (
981+ ( pointColorActive [ i ] || pointColorActive [ 0 ] ) [ 3 ]
982+ ) ;
983+ rgba [ i * 4 + 2 ] = Math . min (
984+ 1 ,
985+ Number . isNaN ( activeOpacity ) ? 1 : activeOpacity
986+ ) ;
913987
914- const hover = Number ( ( pointColorHover [ i ] || pointColorHover [ 0 ] ) [ 3 ] ) ;
915- rgba [ i * 4 + 3 ] = Math . min ( 1 , Number . isNaN ( hover ) ? 1 : hover ) ;
988+ const hoverOpacity = Number (
989+ ( pointColorHover [ i ] || pointColorHover [ 0 ] ) [ 3 ]
990+ ) ;
991+ rgba [ i * 4 + 3 ] = Math . min (
992+ 1 ,
993+ Number . isNaN ( hoverOpacity ) ? 1 : hoverOpacity
994+ ) ;
916995 }
917996
918997 return renderer . regl . texture ( {
@@ -1251,7 +1330,8 @@ const createScatterplot = (
12511330
12521331 return max ( minPointScale , camera . scaling [ 0 ] ) * window . devicePixelRatio ;
12531332 } ;
1254- const getNormalNumPoints = ( ) => numPoints ;
1333+ const getNormalNumPoints = ( ) =>
1334+ filteredPoints && filteredPoints . length ? filteredPoints . length : numPoints ;
12551335 const getSelectedNumPoints = ( ) => selection . length ;
12561336 const getPointOpacityMaxBase = ( ) =>
12571337 getSelectedNumPoints ( ) > 0 ? opacityInactiveMax : 1 ;
@@ -1865,8 +1945,113 @@ const createScatterplot = (
18651945 }
18661946 } ) ;
18671947
1948+ /**
1949+ * Reset the point filter
1950+ * @param {import('./types').ScatterplotMethodOptions['filter'] }
1951+ */
1952+ const unfilter = ( { preventEvent = false } = { } ) => {
1953+ filteredPoints = [ ] ;
1954+ filteredPointSet . clear ( ) ;
1955+ normalPointsIndexBuffer . subdata ( createPointIndex ( numPoints ) ) ;
1956+
1957+ return new Promise ( ( resolve ) => {
1958+ const finish = ( ) => {
1959+ pubSub . subscribe (
1960+ 'draw' ,
1961+ ( ) => {
1962+ if ( ! preventEvent ) pubSub . publish ( 'unfilter' ) ;
1963+ resolve ( ) ;
1964+ } ,
1965+ 1
1966+ ) ;
1967+ draw = true ;
1968+ } ;
1969+
1970+ // Update point connections
1971+ if ( showPointConnections || hasPointConnections ( searchIndex . points [ 0 ] ) ) {
1972+ setPointConnections ( getPoints ( ) ) . then ( ( ) => {
1973+ if ( ! preventEvent ) pubSub . publish ( 'pointConnectionsDraw' ) ;
1974+ finish ( ) ;
1975+ } ) ;
1976+ } else {
1977+ finish ( ) ;
1978+ }
1979+ } ) ;
1980+ } ;
1981+
1982+ /**
1983+ * Filter down to a set of points
1984+ * @param {number | number[] } pointIdxs
1985+ * @param {import('./types').ScatterplotMethodOptions['filter'] }
1986+ */
1987+ const filter = ( pointIdxs , { preventEvent = false } = { } ) => {
1988+ filteredPoints = Array . isArray ( pointIdxs ) ? pointIdxs : [ pointIdxs ] ;
1989+
1990+ if ( filteredPoints . length === 0 ) return unfilter ( { preventEvent } ) ;
1991+
1992+ filteredPointSet . clear ( ) ;
1993+
1994+ const filteredPointsBuffer = [ ] ;
1995+ const filteredSelection = [ ] ;
1996+
1997+ for ( let i = filteredPoints . length - 1 ; i >= 0 ; i -- ) {
1998+ const pointIdx = filteredPoints [ i ] ;
1999+
2000+ if ( pointIdx < 0 || pointIdx >= numPoints ) {
2001+ // Remove invalid filtered points
2002+ filteredPoints . splice ( i , 1 ) ;
2003+ continue ;
2004+ }
2005+
2006+ filteredPointSet . add ( pointIdx ) ;
2007+ filteredPointsBuffer . push . apply (
2008+ filteredPointsBuffer ,
2009+ indexToStateTexCoord ( pointIdx )
2010+ ) ;
2011+
2012+ if ( selectionSet . has ( pointIdx ) ) filteredSelection . push ( pointIdx ) ;
2013+ }
2014+
2015+ // Update the normal points index buffers
2016+ normalPointsIndexBuffer . subdata ( filteredPointsBuffer ) ;
2017+
2018+ // Update selection
2019+ select ( filteredSelection , { preventEvent } ) ;
2020+
2021+ // Unset any potentially hovered point
2022+ hover ( - 1 , { preventEvent } ) ;
2023+
2024+ return new Promise ( ( resolve ) => {
2025+ const finish = ( ) => {
2026+ pubSub . subscribe (
2027+ 'draw' ,
2028+ ( ) => {
2029+ if ( ! preventEvent )
2030+ pubSub . publish ( 'filter' , { points : filteredPoints } ) ;
2031+ resolve ( ) ;
2032+ } ,
2033+ 1
2034+ ) ;
2035+ draw = true ;
2036+ } ;
2037+
2038+ // Update point connections
2039+ if ( showPointConnections || hasPointConnections ( searchIndex . points [ 0 ] ) ) {
2040+ setPointConnections ( getPoints ( ) ) . then ( ( ) => {
2041+ if ( ! preventEvent ) pubSub . publish ( 'pointConnectionsDraw' ) ;
2042+ // We have to re-apply the selection because the connections might
2043+ // have changed
2044+ select ( filteredSelection , { preventEvent } ) ;
2045+ finish ( ) ;
2046+ } ) ;
2047+ } else {
2048+ finish ( ) ;
2049+ }
2050+ } ) ;
2051+ } ;
2052+
18682053 const getPointsInView = ( ) =>
1869- searchIndex . range (
2054+ getPointsInBBox (
18702055 bottomLeftNdc [ 0 ] ,
18712056 bottomLeftNdc [ 1 ] ,
18722057 topRightNdc [ 0 ] ,
@@ -2049,6 +2234,10 @@ const createScatterplot = (
20492234 return ;
20502235 }
20512236
2237+ // Reset filter
2238+ filteredPoints = [ ] ;
2239+ filteredPointSet . clear ( ) ;
2240+
20522241 let pointsCached = false ;
20532242 if ( points ) {
20542243 if ( options . transition ) {
@@ -2472,7 +2661,7 @@ const createScatterplot = (
24722661 showPointConnections = ! ! newShowPointConnections ;
24732662 if ( showPointConnections ) {
24742663 if ( hasPointConnections ( searchIndex . points [ 0 ] ) ) {
2475- setPointConnections ( searchIndex . points ) . then ( ( ) => {
2664+ setPointConnections ( getPoints ( ) ) . then ( ( ) => {
24762665 pubSub . publish ( 'pointConnectionsDraw' ) ;
24772666 draw = true ;
24782667 } ) ;
@@ -2959,51 +3148,6 @@ const createScatterplot = (
29593148 preventEventView = preventEvent ;
29603149 } ;
29613150
2962- /**
2963- * @param {number | number[] } point
2964- * @param {import('./types').ScatterplotMethodOptions['hover'] } options
2965- */
2966- const hover = (
2967- point ,
2968- { showReticleOnce = false , preventEvent = false } = { }
2969- ) => {
2970- let needsRedraw = false ;
2971-
2972- if ( point >= 0 && point < numPoints ) {
2973- needsRedraw = true ;
2974- const oldHoveredPoint = hoveredPoint ;
2975- const newHoveredPoint = point !== hoveredPoint ;
2976- if (
2977- + oldHoveredPoint >= 0 &&
2978- newHoveredPoint &&
2979- ! selectionSet . has ( oldHoveredPoint )
2980- ) {
2981- setPointConnectionColorState ( [ oldHoveredPoint ] , 0 ) ;
2982- }
2983- hoveredPoint = point ;
2984- hoveredPointIndexBuffer . subdata ( indexToStateTexCoord ( point ) ) ;
2985- if ( ! selectionSet . has ( point ) ) setPointConnectionColorState ( [ point ] , 2 ) ;
2986- if ( newHoveredPoint && ! preventEvent )
2987- pubSub . publish ( 'pointover' , hoveredPoint ) ;
2988- } else {
2989- needsRedraw = + hoveredPoint >= 0 ;
2990- if ( needsRedraw ) {
2991- if ( ! selectionSet . has ( hoveredPoint ) ) {
2992- setPointConnectionColorState ( [ hoveredPoint ] , 0 ) ;
2993- }
2994- if ( ! preventEvent ) {
2995- pubSub . publish ( 'pointout' , hoveredPoint ) ;
2996- }
2997- }
2998- hoveredPoint = undefined ;
2999- }
3000-
3001- if ( needsRedraw ) {
3002- draw = true ;
3003- drawReticleOnce = showReticleOnce ;
3004- }
3005- } ;
3006-
30073151 const initCamera = ( ) => {
30083152 if ( ! camera )
30093153 camera = createDom2dCamera ( canvas , { isPanInverted : [ false , true ] } ) ;
@@ -3305,6 +3449,7 @@ const createScatterplot = (
33053449 deselect,
33063450 destroy,
33073451 draw : publicDraw ,
3452+ filter,
33083453 get,
33093454 hover,
33103455 redraw,
@@ -3314,6 +3459,7 @@ const createScatterplot = (
33143459 set,
33153460 export : exportFn ,
33163461 subscribe : pubSub . subscribe ,
3462+ unfilter,
33173463 unsubscribe : pubSub . unsubscribe ,
33183464 view,
33193465 zoomToLocation,
0 commit comments