Skip to content

Instantly share code, notes, and snippets.

@pbogden
Last active April 28, 2016 15:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pbogden/7581689 to your computer and use it in GitHub Desktop.
Save pbogden/7581689 to your computer and use it in GitHub Desktop.
Brush demo

My own little brush demo

brush() creates the following elements

  1. <rect class="background">
  2. <rect class="extent">
  3. <rect class="resize"> -- 2 or 4 (.resize.e .resize.n, .resize.s, .resize.w)

Note: test.html is an annotated version of click-to-recenter brush.

<!doctype html>
<meta charset='utf-8'>
<title>brush</title>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>d3 || document.write('<script src="../d3.min.js"><\/script>')</script>
<body>
<script>
var margin = {top: 100, right: 50, bottom: 200, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.random.normal(height / 2, height / 8);
var brush = d3.svg.brush()
.x(x)
.extent([.3, .5])
.on("brushstart", brushstart)
.on("brush", brushmove)
.on("brushend", brushend);
var label = d3.select('body').append('div')
.style('font-family', 'Verdana,Arial,sans-serif')
.style('font-size', '20px')
.style({position: 'absolute', bottom: '0px' })
.attr('id', 'label');
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.on("click", clicked)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var brushg = svg.append("g")
.call(brush);
brushg.selectAll("rect")
.attr("height", height)
.style('fill', '#ccc');
label.html('ready: ' + brush.extent().map(clean));
function clean(d) {
return d3.round(d,2);
}
function clicked() {
var extent0 = brush.extent();
console.log('clicked -- d3.event', d3.event);
label.html(label.html() + '<br>click: ' + brush.extent().map(clean));
// reset brush width
if (extent0[0] === extent0[1]) {
extent0[0] -= .1;
extent0[1] += .1;
brushg.call(brush.extent(extent0));
}
console.log('clicked -- brush.extent', brush.extent());
}
function brushstart() {
console.log('brushstart -- d3.event', d3.event);
label.html(label.html() + '<br>start: ' + brush.extent().map(clean));
}
function brushmove() {
console.log('brushmove -- d3.event', d3.event);
label.html(label.html() + '<br>moving: ' + brush.extent().map(clean));
}
function brushend() {
console.log('brushend -- d3.event', d3.event);
label.html(label.html() + '<br>end: ' + brush.extent().map(clean));
}
</script>
<!DOCTYPE html>
<meta charset="utf-8">
<title>test</title>
<style>
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
font: 10px sans-serif;
}
.dots {
fill-opacity: .2;
}
.dots .selected {
fill: red;
stroke: brown;
}
.brush .extent {
fill-opacity: .125;
shape-rendering: crispEdges;
}
.resize {
display: inline !important; /* show when empty */
fill: #666;
fill-opacity: .8;
stroke: #000;
stroke-width: 1.5px;
}
</style>
<body>
<script src="../d3/d3.min.js"></script>
<script>
var data = d3.range(800).map(Math.random);
var margin = {top: 194, right: 50, bottom: 214, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
centering = false,
center,
alpha = .01; // determines recenter speed
var x = d3.scale.linear()
.range([0, width]);
var y = d3.random.normal(height / 2, height / 8);
var brush = d3.svg.brush()
.x(x)
.extent([.3, .5])
.on("brush", brushmove);
var arc = d3.svg.arc()
.outerRadius(height / 2)
.startAngle(0)
.endAngle(function(d, i) { return i ? -Math.PI : Math.PI; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis()
.scale(x)
.orient("bottom"));
var dot = svg.append("g")
.attr("class", "dots")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + x(d) + "," + y() + ")"; })
.attr("r", 3.5);
var gBrush = svg.append("g")
.attr("class", "brush")
.call(brush);
gBrush.selectAll(".resize").append("path")
.attr("transform", "translate(0," + height / 2 + ")")
.attr("d", arc);
gBrush.selectAll("rect")
.attr("height", height);
// Add two event listeners to .background
// .brush registers multiple listeners for type event type
// See API reference docs for #selection.on()
gBrush.select(".background")
.on("mousedown.brush", brushcenter)
.on("touchstart.brush", brushcenter);
gBrush.call(brush.event);
function brushmove() {
console.log('brushmove (outer) event:', d3.event.type)
var extent = brush.extent();
dot.classed("selected", function(d) { return extent[0] <= d && d <= extent[1]; });
}
function brushcenter() {
var self = d3.select(window),
target = d3.event.target,
extent = brush.extent(),
size = extent[1] - extent[0],
domain = x.domain(),
x0 = domain[0] + size / 2,
x1 = domain[1] - size / 2;
console.log('brushcenter event:', d3.event.type)
recenter(true);
brushmove();
// This allows continuous brushing anywhere, not just .background
if (d3.event.changedTouches) {
self.on("touchmove.brush", brushmove).on("touchend.brush", brushend);
} else {
self.on("mousemove.brush", brushmove).on("mouseup.brush", brushend);
}
// This brushmove
function brushmove() {
console.log('brushmove (inner) event:', d3.event.type)
// prevent default brush behavior (i.e., to drag a new extent)
d3.event.stopPropagation();
// recenter the element instead
center = Math.max(x0, Math.min(x1, x.invert(d3.mouse(target)[0])));
recenter(false);
}
function brushend() {
console.log('brushend (inner) event:', d3.event.type)
brushmove();
self.on(".brush", null);
}
}
function recenter(smooth) {
if (centering) return; // timer is active and already tweening
if (!smooth) return void tween(1); // instantaneous jump
centering = true;
function tween(alpha) {
var extent = brush.extent(),
size = extent[1] - extent[0],
center1 = center * alpha + (extent[0] + extent[1]) / 2 * (1 - alpha);
gBrush
.call(brush.extent([center1 - size / 2, center1 + size / 2]))
.call(brush.event);
// returns true when essentially centered (inequality evaluates first)
return !(centering = Math.abs(center1 - center) > 1e-3);
}
// invoke function repeatedly until it returns true
// note: not using the function argument (elapsed time in milliseconds)
d3.timer(function() {
return tween(alpha);
});
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment