A variation of the click-to-recenter brush with snapping on brushend.
-
-
Save mbostock/6498580 to your computer and use it in GitHub Desktop.
Click-to-Recenter Brush II
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 | |
redirect: https://observablehq.com/@d3/click-to-recenter-brush?collection=@d3/d3-brush |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.