Skip to content

Instantly share code, notes, and snippets.

@mbostock

mbostock/.block

Last active Aug 8, 2019
Embed
What would you like to do?
Click-to-Recenter Brush
license: gpl-3.0
redirect: https://observablehq.com/@d3/click-to-recenter-brush

By default, clicking and dragging outside the current brush selection drags a new selection. This brush has been modified such that this interaction instead recenters the brush.

This customization is implemented by changing the type of the brush’s overlay element from overlay to selection so that clicking and dragging on the overlay element behaves identically to clicking and dragging on the brush selection. (The overlay element is the invisible rect element that receives pointer events when clicking outside the brush selection.) When the overlay is clicked and before brushing starts, the brush is recentered around the pointer, such that the brush remains centered around the pointer during brushing.

This technique can be composed with snapping on brushend if desired.

<!DOCTYPE html>
<style>
.selected {
fill: red;
stroke: brown;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var randomX = d3.randomUniform(0, 10),
randomY = d3.randomNormal(0.5, 0.12),
data = d3.range(800).map(function() { return [randomX(), randomY()]; });
var svg = d3.select("svg"),
margin = {top: 194, right: 50, bottom: 214, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleLinear()
.domain([0, 10])
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var brush = d3.brushX()
.extent([[0, 0], [width, height]])
.on("start brush", brushed);
var dot = g.append("g")
.attr("fill-opacity", 0.2)
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + x(d[0]) + "," + y(d[1]) + ")"; })
.attr("r", 3.5);
g.append("g")
.call(brush)
.call(brush.move, [3, 5].map(x))
.selectAll(".overlay")
.each(function(d) { d.type = "selection"; }) // Treat overlay interaction as move.
.on("mousedown touchstart", brushcentered); // Recenter before brushing.
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
function brushcentered() {
var dx = x(1) - x(0), // Use a fixed width when recentering.
cx = d3.mouse(this)[0],
x0 = cx - dx / 2,
x1 = cx + dx / 2;
d3.select(this.parentNode).call(brush.move, x1 > width ? [width - dx, width] : x0 < 0 ? [0, dx] : [x0, x1]);
}
function brushed() {
var extent = d3.event.selection.map(x.invert, x);
dot.classed("selected", function(d) { return extent[0] <= d[0] && d[0] <= extent[1]; });
}
</script>
@gamazeps

This comment has been minimized.

Copy link

@gamazeps gamazeps commented Dec 6, 2016

Is there a version of this sample for d3 v4 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment