@@ -5,17 +5,24 @@ function pointerK(kx, ky, {px, py, maxRadius = 40, channels, ...options} = {}) {
5
5
maxRadius = + maxRadius ;
6
6
if ( px != null ) channels = { ...channels , px : { value : px , scale : "x" } } ;
7
7
if ( py != null ) channels = { ...channels , py : { value : py , scale : "y" } } ;
8
+ const stateBySvg = new WeakMap ( ) ;
8
9
return {
9
10
channels,
10
11
...options ,
11
12
render ( index , scales , values , dimensions , context ) {
12
13
const mark = this ;
13
14
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
+
14
22
const faceted = index . fi != null ;
15
- const facetState = faceted ? getFacetState ( mark , svg ) : null ;
23
+ const facetState = faceted ? ( state . facetState ??= new Map ( ) ) : null ;
16
24
const { x : X0 , y : Y0 , x1 : X1 , y1 : Y1 , x2 : X2 , y2 : Y2 , px : X = X0 , py : Y = Y0 } = values ;
17
25
const [ cx , cy ] = applyFrameAnchor ( this , dimensions ) ;
18
- let sticky = false ;
19
26
let i ; // currently focused index
20
27
let g ; // currently rendered mark
21
28
@@ -58,11 +65,12 @@ function pointerK(kx, ky, {px, py, maxRadius = 40, channels, ...options} = {}) {
58
65
}
59
66
g . replaceWith ( r ) ;
60
67
}
68
+ state . roots [ renderIndex ] = r ;
61
69
return ( g = r ) ;
62
70
}
63
71
64
72
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
66
74
const [ xp , yp ] = pointof ( event , faceted ? g : g . parentNode ) ;
67
75
let ii = null ;
68
76
let ri = maxRadius * maxRadius ;
@@ -79,14 +87,16 @@ function pointerK(kx, ky, {px, py, maxRadius = 40, channels, ...options} = {}) {
79
87
80
88
function pointerdown ( event ) {
81
89
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
85
95
}
86
96
87
97
function pointerleave ( event ) {
88
98
if ( event . pointerType !== "mouse" ) return ;
89
- if ( ! sticky ) render ( null ) ;
99
+ if ( ! state . sticky ) render ( null ) ;
90
100
}
91
101
92
102
// We listen to the svg element; listening to the window instead would let
@@ -114,16 +124,3 @@ export function pointerX(options) {
114
124
export function pointerY ( options ) {
115
125
return pointerK ( 0.01 , 1 , options ) ;
116
126
}
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