Skip to content

Instantly share code, notes, and snippets.

@mbostock
Forked from mbostock/.block
Last active August 8, 2019 22:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mbostock/6498580 to your computer and use it in GitHub Desktop.
Save mbostock/6498580 to your computer and use it in GitHub Desktop.
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