@@ -10,6 +10,7 @@ import {
1010} from "d3" ;
1111import { composeRender , Mark } from "../mark.js" ;
1212import { constant , dataify , identity , isIterable , keyword , maybeInterval , maybeTuple , take } from "../options.js" ;
13+ import { pixelRound } from "../precision.js" ;
1314import { applyAttr } from "../style.js" ;
1415
1516const defaults = { ariaLabel : "brush" , fill : "#777" , fillOpacity : 0.3 , stroke : "#fff" } ;
@@ -49,8 +50,8 @@ export class Brush extends Mark {
4950 const dim = this . _dimension ;
5051 const interval = this . _interval ;
5152 if ( context . projection && dim !== "xy" ) throw new Error ( `brush${ dim . toUpperCase ( ) } does not support projections` ) ;
52- const invertX = ( ! context . projection && x ?. invert ) || ( ( d ) => d ) ;
53- const invertY = ( ! context . projection && y ?. invert ) || ( ( d ) => d ) ;
53+ const invertX = precisionInvert ( x , context . projection ) ;
54+ const invertY = precisionInvert ( y , context . projection ) ;
5455 const applyX = ( this . _applyX = ( ! context . projection && x ) || ( ( d ) => d ) ) ;
5556 const applyY = ( this . _applyY = ( ! context . projection && y ) || ( ( d ) => d ) ) ;
5657 context . dispatchValue ( null ) ;
@@ -72,7 +73,10 @@ export class Brush extends Mark {
7273 _brush
7374 . extent ( [
7475 [ dimensions . marginLeft - ( dim !== "y" ) , dimensions . marginTop - ( dim !== "x" ) ] ,
75- [ dimensions . width - dimensions . marginRight + ( dim !== "y" ) , dimensions . height - dimensions . marginBottom + ( dim !== "x" ) ]
76+ [
77+ dimensions . width - dimensions . marginRight + ( dim !== "y" ) ,
78+ dimensions . height - dimensions . marginBottom + ( dim !== "x" )
79+ ]
7680 ] )
7781 . on ( "start brush end" , function ( event ) {
7882 if ( syncing ) return ;
@@ -129,40 +133,28 @@ export class Brush extends Mark {
129133 context . dispatchValue ( value ) ;
130134 }
131135 } else {
132- const [ [ px1 , py1 ] , [ px2 , py2 ] ] =
133- dim === "xy"
134- ? selection
135- : dim === "x"
136- ? [
137- [ selection [ 0 ] , NaN ] ,
138- [ selection [ 1 ] , NaN ]
139- ]
140- : [
141- [ NaN , selection [ 0 ] ] ,
142- [ NaN , selection [ 1 ] ]
143- ] ;
144-
145- const inX = isNaN ( px1 ) ? ( ) => true : ( xi ) => px1 <= xi && xi < px2 ;
146- const inY = isNaN ( py1 ) ? ( ) => true : ( yi ) => py1 <= yi && yi < py2 ;
147-
136+ const [ [ px1 , py1 ] , [ px2 , py2 ] ] = dim === "xy" ? selection
137+ : dim === "x" ? [ [ selection [ 0 ] ] , [ selection [ 1 ] ] ]
138+ : [ [ , selection [ 0 ] ] , [ , selection [ 1 ] ] ] ; // prettier-ignore
139+ const inX = dim !== "y" && ( ( xi ) => px1 <= xi && xi < px2 ) ;
140+ const inY = dim !== "x" && ( ( yi ) => py1 <= yi && yi < py2 ) ;
148141 if ( sync ) {
149142 syncing = true ;
150143 selectAll ( _brushNodes . filter ( ( _ , i ) => i !== currentNode ) ) . call ( _brush . move , selection ) ;
151144 syncing = false ;
152145 }
153146 for ( let i = sync ? 0 : currentNode , n = sync ? _brushNodes . length : currentNode + 1 ; i < n ; ++ i ) {
154147 inactive . update ( false , i ) ;
155- ctx . update ( ( xi , yi ) => ! ( inX ( xi ) && inY ( yi ) ) , i ) ;
156- focus . update ( ( xi , yi ) => inX ( xi ) && inY ( yi ) , i ) ;
148+ ctx . update ( ! inX ? ( _ , yi ) => ! inY ( yi ) : ! inY ? ( xi ) => ! inX ( xi ) : ( xi , yi ) => ! ( inX ( xi ) && inY ( yi ) ) , i ) ;
149+ focus . update ( ! inX ? ( _ , yi ) => inY ( yi ) : ! inY ? inX : ( xi , yi ) => inX ( xi ) && inY ( yi ) , i ) ;
157150 }
158151
159152 const [ x1 , x2 ] = invertX && [ invertX ( px1 ) , invertX ( px2 ) ] . sort ( ascending ) ;
160153 const [ y1 , y2 ] = invertY && [ invertY ( py1 ) , invertY ( py2 ) ] . sort ( ascending ) ;
161154
162155 // Snap to interval on end
163- if ( type === "end" && interval && ! snapping ) {
164- const s1 = dim === "x" ? x1 : y1 ;
165- const s2 = dim === "x" ? x2 : y2 ;
156+ if ( ! snapping && type === "end" && interval ) {
157+ const [ s1 , s2 ] = dim === "x" ? [ x1 , x2 ] : [ y1 , y2 ] ;
166158 const r1 = intervalRound ( interval , s1 ) ;
167159 let r2 = intervalRound ( interval , s2 ) ;
168160 if ( + r1 === + r2 ) r2 = interval . offset ( r1 ) ;
@@ -205,10 +197,9 @@ export class Brush extends Mark {
205197 return ;
206198 }
207199 const { x1, x2, y1, y2, fx, fy} = value ;
208- const node = this . _brushNodes . find ( ( n ) => {
209- const d = n . __data__ ;
210- return ( fx === undefined || d ?. x === fx ) && ( fy === undefined || d ?. y === fy ) ;
211- } ) ;
200+ const node = this . _brushNodes . find (
201+ ( n ) => ( fx === undefined || n . __data__ ?. x === fx ) && ( fy === undefined || n . __data__ ?. y === fy )
202+ ) ;
212203 if ( ! node ) return ;
213204 const [ px1 , px2 ] = [ x1 , x2 ] . map ( this . _applyX ) . sort ( ascending ) ;
214205 const [ py1 , py2 ] = [ y1 , y2 ] . map ( this . _applyY ) . sort ( ascending ) ;
@@ -313,24 +304,22 @@ function renderFilter(initialTest, channelDefaults = {}) {
313304 pointerEvents : "none" ,
314305 ...channelDefaults ,
315306 ...options ,
316- render : composeRender ( function ( index , scales , values , dimensions , context , next ) {
317- const { x : X , y : Y , x1 : X1 , x2 : X2 , y1 : Y1 , y2 : Y2 } = values ;
307+ render : composeRender ( ( index , scales , values , dimensions , context , next ) => {
308+ const { x : X , y : Y , x1 : X1 , x2 : X2 , y1 : Y1 , y2 : Y2 , z : Z } = values ;
318309 const MX = X ?? ( X1 && X2 ? Float64Array . from ( X1 , ( v , i ) => ( v + X2 [ i ] ) / 2 ) : undefined ) ;
319310 const MY = Y ?? ( Y1 && Y2 ? Float64Array . from ( Y1 , ( v , i ) => ( v + Y2 [ i ] ) / 2 ) : undefined ) ;
311+ const G = Array ( ( MX ?? MY ) . length ) ;
320312 const render = ( test ) => {
321313 if ( typeof test !== "function" ) test = constant ( test ) ;
322- let run = [ ] ;
323- const runs = [ run ] ;
314+ const I = [ ] ;
315+ let run = 0 ;
324316 for ( const i of index ) {
325- if ( test ( MX ?. [ i ] , MY ?. [ i ] ) ) run . push ( i ) ;
326- else if ( run . length ) runs . push ( ( run = [ ] ) ) ;
317+ if ( test ( MX ?. [ i ] , MY ?. [ i ] ) ) {
318+ I . push ( i ) ;
319+ G [ i ] = `${ Z ?. [ i ] ?? "" } /${ run } ` ;
320+ } else ++ run ;
327321 }
328- const g = next ( runs [ 0 ] , scales , values , dimensions , context ) ;
329- for ( const run of runs . slice ( 1 ) ) {
330- const h = next ( run , scales , values , dimensions , context ) ;
331- while ( h . firstChild ) g . appendChild ( h . firstChild ) ;
332- }
333- return g ;
322+ return next ( I , scales , { ...values , z : G } , dimensions , context ) ;
334323 } ;
335324 let g = render ( initialTest ) ;
336325 updatePerFacet . push ( ( test ) => {
@@ -349,3 +338,9 @@ function renderFilter(initialTest, channelDefaults = {}) {
349338 }
350339 ) ;
351340}
341+
342+ function precisionInvert ( scale , projection ) {
343+ if ( projection || ! scale ?. invert ) return ( d ) => d ;
344+ const round = pixelRound ( scale ) ;
345+ return ( p ) => round ( scale . invert ( p ) ) ;
346+ }
0 commit comments