@@ -10,6 +10,7 @@ import {
1010} from "d3" ;
1111import { composeRender , Mark } from "../mark.js" ;
1212import { constant , keyword , maybeInterval } from "../options.js" ;
13+ import { pixelPrecision } from "../precision.js" ;
1314
1415export class Brush extends Mark {
1516 constructor ( { dimension = "xy" , interval, sync = false } = { } ) {
@@ -32,8 +33,8 @@ export class Brush extends Mark {
3233 const dim = this . _dimension ;
3334 const interval = this . _interval ;
3435 if ( context . projection && dim !== "xy" ) throw new Error ( `brush${ dim . toUpperCase ( ) } does not support projections` ) ;
35- const invertX = ( ! context . projection && x ?. invert ) || ( ( d ) => d ) ;
36- const invertY = ( ! context . projection && y ?. invert ) || ( ( d ) => d ) ;
36+ const invertX = precisionInvert ( x , context . projection ) ;
37+ const invertY = precisionInvert ( y , context . projection ) ;
3738 const applyX = ( this . _applyX = ( ! context . projection && x ) || ( ( d ) => d ) ) ;
3839 const applyY = ( this . _applyY = ( ! context . projection && y ) || ( ( d ) => d ) ) ;
3940 context . dispatchValue ( null ) ;
@@ -43,7 +44,10 @@ export class Brush extends Mark {
4344 _brush
4445 . extent ( [
4546 [ dimensions . marginLeft - ( dim !== "y" ) , dimensions . marginTop - ( dim !== "x" ) ] ,
46- [ dimensions . width - dimensions . marginRight + ( dim !== "y" ) , dimensions . height - dimensions . marginBottom + ( dim !== "x" ) ]
47+ [
48+ dimensions . width - dimensions . marginRight + ( dim !== "y" ) ,
49+ dimensions . height - dimensions . marginBottom + ( dim !== "x" )
50+ ]
4751 ] )
4852 . on ( "start brush end" , function ( event ) {
4953 if ( syncing ) return ;
@@ -99,40 +103,28 @@ export class Brush extends Mark {
99103 context . dispatchValue ( value ) ;
100104 }
101105 } else {
102- const [ [ px1 , py1 ] , [ px2 , py2 ] ] =
103- dim === "xy"
104- ? selection
105- : dim === "x"
106- ? [
107- [ selection [ 0 ] , NaN ] ,
108- [ selection [ 1 ] , NaN ]
109- ]
110- : [
111- [ NaN , selection [ 0 ] ] ,
112- [ NaN , selection [ 1 ] ]
113- ] ;
114-
115- const inX = isNaN ( px1 ) ? ( ) => true : ( xi ) => px1 <= xi && xi < px2 ;
116- const inY = isNaN ( py1 ) ? ( ) => true : ( yi ) => py1 <= yi && yi < py2 ;
117-
106+ const [ [ px1 , py1 ] , [ px2 , py2 ] ] = dim === "xy" ? selection
107+ : dim === "x" ? [ [ selection [ 0 ] ] , [ selection [ 1 ] ] ]
108+ : [ [ , selection [ 0 ] ] , [ , selection [ 1 ] ] ] ; // prettier-ignore
109+ const inX = dim !== "y" && ( ( xi ) => px1 <= xi && xi < px2 ) ;
110+ const inY = dim !== "x" && ( ( yi ) => py1 <= yi && yi < py2 ) ;
118111 if ( sync ) {
119112 syncing = true ;
120113 selectAll ( _brushNodes . filter ( ( _ , i ) => i !== currentNode ) ) . call ( _brush . move , selection ) ;
121114 syncing = false ;
122115 }
123116 for ( let i = sync ? 0 : currentNode , n = sync ? _brushNodes . length : currentNode + 1 ; i < n ; ++ i ) {
124117 inactive . update ( false , i ) ;
125- ctx . update ( ( xi , yi ) => ! ( inX ( xi ) && inY ( yi ) ) , i ) ;
126- focus . update ( ( xi , yi ) => inX ( xi ) && inY ( yi ) , i ) ;
118+ ctx . update ( ! inX ? ( _ , yi ) => ! inY ( yi ) : ! inY ? ( xi ) => ! inX ( xi ) : ( xi , yi ) => ! ( inX ( xi ) && inY ( yi ) ) , i ) ;
119+ focus . update ( ! inX ? ( _ , yi ) => inY ( yi ) : ! inY ? inX : ( xi , yi ) => inX ( xi ) && inY ( yi ) , i ) ;
127120 }
128121
129122 const [ x1 , x2 ] = invertX && [ invertX ( px1 ) , invertX ( px2 ) ] . sort ( ascending ) ;
130123 const [ y1 , y2 ] = invertY && [ invertY ( py1 ) , invertY ( py2 ) ] . sort ( ascending ) ;
131124
132125 // Snap to interval on end
133- if ( type === "end" && interval && ! snapping ) {
134- const s1 = dim === "x" ? x1 : y1 ;
135- const s2 = dim === "x" ? x2 : y2 ;
126+ if ( ! snapping && type === "end" && interval ) {
127+ const [ s1 , s2 ] = dim === "x" ? [ x1 , x2 ] : [ y1 , y2 ] ;
136128 const r1 = intervalRound ( interval , s1 ) ;
137129 let r2 = intervalRound ( interval , s2 ) ;
138130 if ( + r1 === + r2 ) r2 = interval . offset ( r1 ) ;
@@ -168,10 +160,9 @@ export class Brush extends Mark {
168160 return ;
169161 }
170162 const { x1, x2, y1, y2, fx, fy} = value ;
171- const node = this . _brushNodes . find ( ( n ) => {
172- const d = n . __data__ ;
173- return ( fx === undefined || d ?. x === fx ) && ( fy === undefined || d ?. y === fy ) ;
174- } ) ;
163+ const node = this . _brushNodes . find (
164+ ( n ) => ( fx === undefined || n . __data__ ?. x === fx ) && ( fy === undefined || n . __data__ ?. y === fy )
165+ ) ;
175166 if ( ! node ) return ;
176167 const [ px1 , px2 ] = [ x1 , x2 ] . map ( this . _applyX ) . sort ( ascending ) ;
177168 const [ py1 , py2 ] = [ y1 , y2 ] . map ( this . _applyY ) . sort ( ascending ) ;
@@ -266,24 +257,22 @@ function renderFilter(initialTest) {
266257 return {
267258 pointerEvents : "none" ,
268259 ...options ,
269- render : composeRender ( function ( index , scales , values , dimensions , context , next ) {
270- const { x : X , y : Y , x1 : X1 , x2 : X2 , y1 : Y1 , y2 : Y2 } = values ;
260+ render : composeRender ( ( index , scales , values , dimensions , context , next ) => {
261+ const { x : X , y : Y , x1 : X1 , x2 : X2 , y1 : Y1 , y2 : Y2 , z : Z } = values ;
271262 const MX = X ?? ( X1 && X2 ? Float64Array . from ( X1 , ( v , i ) => ( v + X2 [ i ] ) / 2 ) : undefined ) ;
272263 const MY = Y ?? ( Y1 && Y2 ? Float64Array . from ( Y1 , ( v , i ) => ( v + Y2 [ i ] ) / 2 ) : undefined ) ;
264+ const G = Array ( ( MX ?? MY ) . length ) ;
273265 const render = ( test ) => {
274266 if ( typeof test !== "function" ) test = constant ( test ) ;
275- let run = [ ] ;
276- const runs = [ run ] ;
267+ const I = [ ] ;
268+ let run = 0 ;
277269 for ( const i of index ) {
278- if ( test ( MX ?. [ i ] , MY ?. [ i ] ) ) run . push ( i ) ;
279- else if ( run . length ) runs . push ( ( run = [ ] ) ) ;
270+ if ( test ( MX ?. [ i ] , MY ?. [ i ] ) ) {
271+ I . push ( i ) ;
272+ G [ i ] = `${ Z ?. [ i ] ?? "" } /${ run } ` ;
273+ } else ++ run ;
280274 }
281- const g = next ( runs [ 0 ] , scales , values , dimensions , context ) ;
282- for ( const run of runs . slice ( 1 ) ) {
283- const h = next ( run , scales , values , dimensions , context ) ;
284- while ( h . firstChild ) g . appendChild ( h . firstChild ) ;
285- }
286- return g ;
275+ return next ( I , scales , { ...values , z : G } , dimensions , context ) ;
287276 } ;
288277 let g = render ( initialTest ) ;
289278 updatePerFacet . push ( ( test ) => {
@@ -302,3 +291,10 @@ function renderFilter(initialTest) {
302291 }
303292 ) ;
304293}
294+
295+ function precisionInvert ( scale , projection ) {
296+ if ( projection || ! scale ?. invert ) return ( d ) => d ;
297+ const interval = pixelPrecision ( scale ) ;
298+ if ( interval == null ) return scale . invert . bind ( scale ) ;
299+ return ( p ) => interval . floor ( scale . invert ( p ) ) ;
300+ }
0 commit comments