@@ -5,17 +5,24 @@ function pointerK(kx, ky, {px, py, maxRadius = 40, channels, ...options} = {}) {
55 maxRadius = + maxRadius ;
66 if ( px != null ) channels = { ...channels , px : { value : px , scale : "x" } } ;
77 if ( py != null ) channels = { ...channels , py : { value : py , scale : "y" } } ;
8+ const stateBySvg = new WeakMap ( ) ;
89 return {
910 channels,
1011 ...options ,
1112 render ( index , scales , values , dimensions , context ) {
1213 const mark = this ;
1314 const svg = context . ownerSVGElement ;
15+
16+ // Isolate state per-pointer, per-plot; if the pointer is reused by
17+ // multiple marks, they will share the same state (e.g., sticky modality).
18+ let state = stateBySvg . get ( svg ) ;
19+ if ( ! state ) stateBySvg . set ( svg , ( state = { sticky : false , roots : [ ] , renders : [ ] } ) ) ;
20+ let renderIndex = state . renders . push ( render ) - 1 ;
21+
1422 const faceted = index . fi != null ;
15- const facetState = faceted ? getFacetState ( mark , svg ) : null ;
23+ const facetState = faceted ? ( state . facetState ??= new Map ( ) ) : null ;
1624 const { x : X0 , y : Y0 , x1 : X1 , y1 : Y1 , x2 : X2 , y2 : Y2 , px : X = X0 , py : Y = Y0 } = values ;
1725 const [ cx , cy ] = applyFrameAnchor ( this , dimensions ) ;
18- let sticky = false ;
1926 let i ; // currently focused index
2027 let g ; // currently rendered mark
2128
@@ -58,11 +65,12 @@ function pointerK(kx, ky, {px, py, maxRadius = 40, channels, ...options} = {}) {
5865 }
5966 g . replaceWith ( r ) ;
6067 }
68+ state . roots [ renderIndex ] = r ;
6169 return ( g = r ) ;
6270 }
6371
6472 function pointermove ( event ) {
65- if ( sticky || ( event . pointerType === "mouse" && event . buttons === 1 ) ) return ; // dragging
73+ if ( state . sticky || ( event . pointerType === "mouse" && event . buttons === 1 ) ) return ; // dragging
6674 const [ xp , yp ] = pointof ( event , faceted ? g : g . parentNode ) ;
6775 let ii = null ;
6876 let ri = maxRadius * maxRadius ;
@@ -79,14 +87,16 @@ function pointerK(kx, ky, {px, py, maxRadius = 40, channels, ...options} = {}) {
7987
8088 function pointerdown ( event ) {
8189 if ( event . pointerType !== "mouse" ) return ;
82- if ( sticky && g . contains ( event . target ) ) return ; // stay sticky
83- if ( sticky ) ( sticky = false ) , render ( null ) ;
84- else if ( i != null ) ( sticky = true ) , facetState ?. set ( index . fi , - 1 ) ; // suppress other facets
90+ if ( i == null ) return ; // not pointing
91+ if ( state . sticky && state . roots . some ( ( r ) => r ?. contains ( event . target ) ) ) return ; // stay sticky
92+ if ( state . sticky ) ( state . sticky = false ) , state . renders . forEach ( ( r ) => r ( null ) ) ; // clear all pointers
93+ else state . sticky = true ;
94+ event . stopImmediatePropagation ( ) ; // suppress other pointers
8595 }
8696
8797 function pointerleave ( event ) {
8898 if ( event . pointerType !== "mouse" ) return ;
89- if ( ! sticky ) render ( null ) ;
99+ if ( ! state . sticky ) render ( null ) ;
90100 }
91101
92102 // We listen to the svg element; listening to the window instead would let
@@ -114,16 +124,3 @@ export function pointerX(options) {
114124export function pointerY ( options ) {
115125 return pointerK ( 0.01 , 1 , options ) ;
116126}
117-
118- const facetStateByMark = new WeakMap ( ) ;
119-
120- // This isolates facet state per-mark, per-plot. Most of the time a separate
121- // pointer will be instantiated per mark, but it’s possible to reuse the same
122- // pointer instance with multiple marks so we protect against it.
123- function getFacetState ( mark , svg ) {
124- let stateBySvg = facetStateByMark . get ( mark ) ;
125- if ( ! stateBySvg ) facetStateByMark . set ( mark , ( stateBySvg = new WeakMap ( ) ) ) ;
126- let state = stateBySvg . get ( svg ) ;
127- if ( ! state ) stateBySvg . set ( svg , ( state = new Map ( ) ) ) ;
128- return state ;
129- }
0 commit comments