@@ -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 ;
@@ -131,40 +135,28 @@ export class Brush extends Mark {
131135 context . dispatchValue ( value ) ;
132136 }
133137 } else {
134- const [ [ px1 , py1 ] , [ px2 , py2 ] ] =
135- dim === "xy"
136- ? selection
137- : dim === "x"
138- ? [
139- [ selection [ 0 ] , NaN ] ,
140- [ selection [ 1 ] , NaN ]
141- ]
142- : [
143- [ NaN , selection [ 0 ] ] ,
144- [ NaN , selection [ 1 ] ]
145- ] ;
146-
147- const inX = isNaN ( px1 ) ? ( ) => true : ( xi ) => px1 <= xi && xi < px2 ;
148- const inY = isNaN ( py1 ) ? ( ) => true : ( yi ) => py1 <= yi && yi < py2 ;
149-
138+ const [ [ px1 , py1 ] , [ px2 , py2 ] ] = dim === "xy" ? selection
139+ : dim === "x" ? [ [ selection [ 0 ] ] , [ selection [ 1 ] ] ]
140+ : [ [ , selection [ 0 ] ] , [ , selection [ 1 ] ] ] ; // prettier-ignore
141+ const inX = dim !== "y" && ( ( xi ) => px1 <= xi && xi < px2 ) ;
142+ const inY = dim !== "x" && ( ( yi ) => py1 <= yi && yi < py2 ) ;
150143 if ( sync ) {
151144 syncing = true ;
152145 selectAll ( _brushNodes . filter ( ( _ , i ) => i !== currentNode ) ) . call ( _brush . move , selection ) ;
153146 syncing = false ;
154147 }
155148 for ( let i = sync ? 0 : currentNode , n = sync ? _brushNodes . length : currentNode + 1 ; i < n ; ++ i ) {
156149 inactive . update ( false , i ) ;
157- ctx . update ( ( xi , yi ) => ! ( inX ( xi ) && inY ( yi ) ) , i ) ;
158- focus . update ( ( xi , yi ) => inX ( xi ) && inY ( yi ) , i ) ;
150+ ctx . update ( ! inX ? ( _ , yi ) => ! inY ( yi ) : ! inY ? ( xi ) => ! inX ( xi ) : ( xi , yi ) => ! ( inX ( xi ) && inY ( yi ) ) , i ) ;
151+ focus . update ( ! inX ? ( _ , yi ) => inY ( yi ) : ! inY ? inX : ( xi , yi ) => inX ( xi ) && inY ( yi ) , i ) ;
159152 }
160153
161154 const [ x1 , x2 ] = invertX && [ invertX ( px1 ) , invertX ( px2 ) ] . sort ( ascending ) ;
162155 const [ y1 , y2 ] = invertY && [ invertY ( py1 ) , invertY ( py2 ) ] . sort ( ascending ) ;
163156
164157 // Snap to interval on end
165- if ( type === "end" && interval && ! snapping ) {
166- const s1 = dim === "x" ? x1 : y1 ;
167- const s2 = dim === "x" ? x2 : y2 ;
158+ if ( ! snapping && type === "end" && interval ) {
159+ const [ s1 , s2 ] = dim === "x" ? [ x1 , x2 ] : [ y1 , y2 ] ;
168160 const r1 = intervalRound ( interval , s1 ) ;
169161 let r2 = intervalRound ( interval , s2 ) ;
170162 if ( + r1 === + r2 ) r2 = interval . offset ( r1 ) ;
@@ -207,10 +199,9 @@ export class Brush extends Mark {
207199 return ;
208200 }
209201 const { x1, x2, y1, y2, fx, fy} = value ;
210- const node = this . _brushNodes . find ( ( n ) => {
211- const d = n . __data__ ;
212- return ( fx === undefined || d ?. x === fx ) && ( fy === undefined || d ?. y === fy ) ;
213- } ) ;
202+ const node = this . _brushNodes . find (
203+ ( n ) => ( fx === undefined || n . __data__ ?. x === fx ) && ( fy === undefined || n . __data__ ?. y === fy )
204+ ) ;
214205 if ( ! node ) return ;
215206 const [ px1 , px2 ] = [ x1 , x2 ] . map ( this . _applyX ) . sort ( ascending ) ;
216207 const [ py1 , py2 ] = [ y1 , y2 ] . map ( this . _applyY ) . sort ( ascending ) ;
@@ -315,24 +306,22 @@ function renderFilter(initialTest, channelDefaults = {}) {
315306 pointerEvents : "none" ,
316307 ...channelDefaults ,
317308 ...options ,
318- render : composeRender ( function ( index , scales , values , dimensions , context , next ) {
319- const { x : X , y : Y , x1 : X1 , x2 : X2 , y1 : Y1 , y2 : Y2 } = values ;
309+ render : composeRender ( ( index , scales , values , dimensions , context , next ) => {
310+ const { x : X , y : Y , x1 : X1 , x2 : X2 , y1 : Y1 , y2 : Y2 , z : Z } = values ;
320311 const MX = X ?? ( X1 && X2 ? Float64Array . from ( X1 , ( v , i ) => ( v + X2 [ i ] ) / 2 ) : undefined ) ;
321312 const MY = Y ?? ( Y1 && Y2 ? Float64Array . from ( Y1 , ( v , i ) => ( v + Y2 [ i ] ) / 2 ) : undefined ) ;
313+ const G = Array ( ( MX ?? MY ) . length ) ;
322314 const render = ( test ) => {
323315 if ( typeof test !== "function" ) test = constant ( test ) ;
324- let run = [ ] ;
325- const runs = [ run ] ;
316+ const I = [ ] ;
317+ let run = 0 ;
326318 for ( const i of index ) {
327- if ( test ( MX ?. [ i ] , MY ?. [ i ] ) ) run . push ( i ) ;
328- else if ( run . length ) runs . push ( ( run = [ ] ) ) ;
319+ if ( test ( MX ?. [ i ] , MY ?. [ i ] ) ) {
320+ I . push ( i ) ;
321+ G [ i ] = `${ Z ?. [ i ] ?? "" } /${ run } ` ;
322+ } else ++ run ;
329323 }
330- const g = next ( runs [ 0 ] , scales , values , dimensions , context ) ;
331- for ( const run of runs . slice ( 1 ) ) {
332- const h = next ( run , scales , values , dimensions , context ) ;
333- while ( h . firstChild ) g . appendChild ( h . firstChild ) ;
334- }
335- return g ;
324+ return next ( I , scales , { ...values , z : G } , dimensions , context ) ;
336325 } ;
337326 let g = render ( initialTest ) ;
338327 updatePerFacet . push ( ( test ) => {
@@ -351,3 +340,9 @@ function renderFilter(initialTest, channelDefaults = {}) {
351340 }
352341 ) ;
353342}
343+
344+ function precisionInvert ( scale , projection ) {
345+ if ( projection || ! scale ?. invert ) return ( d ) => d ;
346+ const round = pixelRound ( scale ) ;
347+ return ( p ) => round ( scale . invert ( p ) ) ;
348+ }
0 commit comments