d3-brush
Interactive brush selection for one- and two-dimensional regions in D3 visualizations, driven by mouse or touch.
Repository Health
Technical Analysis
d3-brush provides the interaction logic behind draggable, resizable selection regions in D3-based visualizations — the rectangle you drag across a scatterplot to select points, or the handles you drag to zoom into a range on a time-series chart. It exposes three constructors: brush() for two-dimensional selections, and brushX() and brushY() for selections constrained to a single axis, each returning a reusable behavior applied to an SVG or HTML element via selection.call().
Under the hood it composes d3-drag for pointer tracking, d3-dispatch for the start/brush/end event lifecycle, d3-interpolate for animated transitions between selection extents, and d3-selection/d3-transition for DOM updates. Applications rarely reach for d3-brush directly for anything beyond selection UI, but it is a foundational building block for cross-filtering dashboards, zoomable timelines, and linked-selection scatterplot matrices throughout the D3 ecosystem.
What You Get
- brush(), brushX(), and brushY() constructors for 2D, horizontal, and vertical selection behaviors
- Drag-to-select, drag-to-resize, and drag-to-move interactions on the same selection out of the box
- A start/brush/end event lifecycle via d3-dispatch for reacting to selection changes in real time
- brushSelection() and brush.move() helpers to read or set the current selection extent imperatively
Common Use Cases
- Letting users rubber-band-select points in a scatterplot to filter a linked table or chart
- Adding a draggable time-range brush beneath a time-series chart to zoom the main view
- Cross-filtering dashboards where dragging a selection in one chart highlights matching data in others
- Building focus-plus-context navigation, where a small overview chart drives a detailed zoomed view
Under The Hood
Architecture
d3-brush centers on a single brush(dim) factory closed over a dimension descriptor — X, Y, or XY objects in src/brush.js that define which handles exist (“w”/“e” for X, “n”/“s” for Y, all eight compass points for XY) and how a raw pointer extent maps to a stored selection. This one abstraction is what lets brush(), brushX(), and brushY() share nearly all their drag, resize, and redraw logic while behaving as three distinct public APIs. Per-node state (the current selection, extent, and active handles) is attached directly to the DOM node under a __brush property, mirroring d3.local’s pattern, and drives an enter/update/exit redraw of overlay, selection, and handle rectangles on every interaction. The overall shape is a stateful interaction controller layered on top of d3-selection’s declarative DOM binding, not a data-driven rendering component.
Tech Stack
The module is plain ES2015+ JavaScript ("type": "module" in package.json) with runtime dependencies on d3-dispatch, d3-drag, d3-interpolate, d3-selection, and d3-transition — all peer modules from the same D3 monorepo family, version-pinned to loose semver ranges (e.g. “2 - 3”) to stay compatible across the D3 v6/v7 line. Rollup with rollup-plugin-terser builds the UMD bundle published for jsdelivr/unpkg consumption, while the ES module entry (src/index.js) is what bundler-based consumers import directly. Mocha runs the test suite and ESLint lints src and test as part of the same yarn test script, both wired into a GitHub Actions Node.js CI workflow.
Code Quality The repository ships a single test file, export-test.js, which verifies the module’s public exports rather than exercising drag/resize/move behavior — unsurprising for a library whose core logic is pointer-driven DOM interaction that’s awkward to unit test without a browser harness, but it means behavioral regressions in brush.js itself have no automated safety net beyond manual/visual testing and downstream consumer bug reports. There is no TypeScript or type annotations; correctness instead leans on ESLint (enforced in CI) and the small, stable surface area of a module that has seen very few structural changes since 2016. Error handling is minimal by design — the module assumes well-formed extents and DOM state rather than validating inputs defensively.
What Makes It Unique d3-brush’s distinguishing choice is unifying three selection behaviors (2D, X-only, Y-only) behind one dimension-descriptor abstraction instead of three separate implementations, plus reusing d3-drag’s own pointer-tracking rather than reimplementing mouse/touch handling — a deliberate “do one interaction well and compose with sibling D3 modules” philosophy rather than a novel interaction technique. It isn’t algorithmically innovative; its value is as the de facto standard building block the wider visualization ecosystem (Observable Plot, countless D3 dashboards) relies on for brushing, rather than something a general charting library reimplements from scratch.