Skip to content

Instantly share code, notes, and snippets.

@mbostock
Forked from mbostock/.block
Last active August 8, 2019 22:19
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
Click-to-Recenter Brush II
license: gpl-3.0
redirect: https://observablehq.com/@d3/click-to-recenter-brush?collection=@d3/d3-brush
<!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)
.on("end", brushended);
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() {
if (!d3.event.selection) return; // Ignore empty selections.
var extent = d3.event.selection.map(x.invert, x);
dot.classed("selected", function(d) { return extent[0] <= d[0] && d[0] <= extent[1]; });
}
function brushended() {
if (!d3.event.sourceEvent) return; // Only transition after input.
if (!d3.event.selection) return; // Ignore empty selections.
var d0 = d3.event.selection.map(x.invert),
d1 = d0.map(Math.round);
// If empty when rounded, use floor & offset instead.
if (d1[0] >= d1[1]) {
d1[0] = Math.floor(d0[0]);
d1[1] = d1[0] + 1;
}
d3.select(this).transition().call(brush.move, d1.map(x));
}
</script>
@bogdan-iuga
Copy link

Hi Mike, I don't know if this is the right place to be asking you this, but can you help me understand why the brush.move will fire in the end two 'end' events?
Thanks, and again, sorry if this is not the right place to do this.

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