Skip to content

dataless brush#2360

Draft
Fil wants to merge 19 commits intomainfrom
fil/brush-dataless
Draft

dataless brush#2360
Fil wants to merge 19 commits intomainfrom
fil/brush-dataless

Conversation

@Fil
Copy link
Contributor

@Fil Fil commented Feb 13, 2026

A brush mark that renders a 2D rectangular brush, enabling selection and filtering. The brush mark has aria-label="brush". The mark does not take data; instead, it offers a mechanism to filter other marks while brushing, as well as any dataset.

The brush dispatches input events with the selection bounds {x1, x2, y1, y2} in data space and a filter function to test whether a point falls within the selection.

Brushes support faceted plots: each facet gets its own brush, starting one clears the others, and the value includes fx/fy as relevant (with support in the filter function).

Three reactive mark options transforms — brush.inactive, brush.context, brush.focus — allow marks to respond to state by showing/hiding points inside or outside the selection. Reactive marks re-render as the brush moves, with pointerEvents set to "none" by default so they don't obstruct interaction if they are above the brush (which achieves better contrast in general).

The brush supports geographic projections: in that case, though, the {x1, x2, y1, y2} coordinates are returned in pixel space; but the filter projects lon/lat automatically for hit-testing.

A pending flag distinguishes brushing events (user gesture) from committed selections when the brush gesture ends and the user releases the pointer. This is to allow workflows where we don't want to rerender continuously during brushing (e.g. to avoid costly calls to a database backend).

The brush has a move property that allows the selection to be modified programmatically.

Alternatives considered:

  • Instead of brush.move we could imagine supporting a plot.value = {} setter. I've investigated this, and the code I came up with felt brittle. Furthermore, it seems more discoverable to use brush.move, and for advanced applications people will want to create the brush outside of Plot anyway. Another thing that this makes unclear, is what happens when the brush mark is reused. (But we can still add this later if necessary.)
  • a .done instead of the .pending flag (with opposite logic); it feels equivalent, no preference.

Ideas for future PRs:

  • Support (options) - control the brush's appearance;
  • Data-based brush — support both (options) and (data, options?) signatures; when data is passed, return filtered data as value.data.
  • brushX, brushY — directional brush variants, maybe with options (frameAnchor, snap, etc.)
  • neutralize tip interaction — a mechanism to suppress tips during brushing (tip+brush combination #2361)
  • Z awareness — per Plot PR z-aware pointer #1671; consider z option for reactive marks
  • Intersection algorithm — support complex shapes (lines); currently only point-in-rect
  • pixel precision inverse — round inverted values to the minimal precision that distinguishes half-pixels; for example: numbers as integers, dates as days. Needs a bit of research. Also we want uniform precision for linear/utc scales.

AI disclosure: I used @claude to help me generate the unit tests and maintain an action plan.

closes #5
supersedes #71
supersedes #1653
needs #2038

Fil added 17 commits February 12, 2026 17:55
this might be a bit controversial, but it feels much easier to do the right thing by default than to have to teach it
rationale: I thought it would help a bit with the problem of the RHS points not being selectable when they define the extent. However, the points on the rhs of the domain will still be brushable since we added a small padding to the brush extent.
@Fil Fil requested a review from mbostock February 13, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Brushing

1 participant