1
d3.svg.brush = function() {
2
var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"),
3
x = null, // x-scale, optional
4
y = null, // y-scale, optional
5
resizes = d3_svg_brushResizes[0],
6
extent = [[0, 0], [0, 0]], // [x0, y0], [x1, y1], in pixels (integers)
7
extentDomain; // the extent in data space, lazily created
11
var g = d3.select(this),
12
bg = g.selectAll(".background").data([0]),
13
fg = g.selectAll(".extent").data([0]),
14
tz = g.selectAll(".resize").data(resizes, String),
17
// Prepare the brush container for events.
19
.style("pointer-events", "all")
20
.on("mousedown.brush", brushstart)
21
.on("touchstart.brush", brushstart);
23
// An invisible, mouseable area for starting a new brush.
24
bg.enter().append("rect")
25
.attr("class", "background")
26
.style("visibility", "hidden")
27
.style("cursor", "crosshair");
29
// The visible brush extent; style this as you like!
30
fg.enter().append("rect")
31
.attr("class", "extent")
32
.style("cursor", "move");
34
// More invisible rects for resizing the extent.
35
tz.enter().append("g")
36
.attr("class", function(d) { return "resize " + d; })
37
.style("cursor", function(d) { return d3_svg_brushCursor[d]; })
39
.attr("x", function(d) { return /[ew]$/.test(d) ? -3 : null; })
40
.attr("y", function(d) { return /^[ns]/.test(d) ? -3 : null; })
43
.style("visibility", "hidden");
45
// Show or hide the resizers.
46
tz.style("display", brush.empty() ? "none" : null);
48
// Remove any superfluous resizers.
51
// Initialize the background to fill the defined range.
52
// If the range isn't defined, you can post-process.
55
bg.attr("x", e[0]).attr("width", e[1] - e[0]);
60
bg.attr("y", e[0]).attr("height", e[1] - e[0]);
68
g.selectAll(".resize").attr("transform", function(d) {
69
return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")";
74
g.select(".extent").attr("x", extent[0][0]);
75
g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]);
79
g.select(".extent").attr("y", extent[0][1]);
80
g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]);
83
function brushstart() {
85
eventTarget = d3.select(d3.event.target),
86
event_ = event.of(target, arguments),
87
g = d3.select(target),
88
resizing = eventTarget.datum(),
89
resizingX = !/^(n|s)$/.test(resizing) && x,
90
resizingY = !/^(e|w)$/.test(resizing) && y,
91
dragging = eventTarget.classed("extent"),
96
var w = d3.select(window)
97
.on("mousemove.brush", brushmove)
98
.on("mouseup.brush", brushend)
99
.on("touchmove.brush", brushmove)
100
.on("touchend.brush", brushend)
101
.on("keydown.brush", keydown)
102
.on("keyup.brush", keyup);
104
// If the extent was clicked on, drag rather than brush;
105
// store the point between the mouse and extent origin instead.
107
origin[0] = extent[0][0] - origin[0];
108
origin[1] = extent[0][1] - origin[1];
111
// If a resizer was clicked on, record which side is to be resized.
112
// Also, set the origin to the opposite side.
114
var ex = +/w$/.test(resizing),
115
ey = +/^n/.test(resizing);
116
offset = [extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1]];
117
origin[0] = extent[ex][0];
118
origin[1] = extent[ey][1];
121
// If the ALT key is down when starting a brush, the center is at the mouse.
122
else if (d3.event.altKey) center = origin.slice();
124
// Propagate the active cursor to the body for the drag duration.
125
g.style("pointer-events", "none").selectAll(".resize").style("display", null);
126
d3.select("body").style("cursor", eventTarget.style("cursor"));
129
event_({type: "brushstart"});
134
var touches = d3.event.changedTouches;
135
return touches ? d3.touches(target, touches)[0] : d3.mouse(target);
139
if (d3.event.keyCode == 32) {
142
origin[0] -= extent[1][0];
143
origin[1] -= extent[1][1];
151
if (d3.event.keyCode == 32 && dragging == 2) {
152
origin[0] += extent[1][0];
153
origin[1] += extent[1][1];
159
function brushmove() {
163
// Preserve the offset for thick resizers.
165
point[0] += offset[0];
166
point[1] += offset[1];
171
// If needed, determine the center from the current extent.
172
if (d3.event.altKey) {
173
if (!center) center = [(extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2];
175
// Update the origin, for when the ALT key is released.
176
origin[0] = extent[+(point[0] < center[0])][0];
177
origin[1] = extent[+(point[1] < center[1])][1];
180
// When the ALT key is released, we clear the center.
184
// Update the brush extent for each dimension.
185
if (resizingX && move1(point, x, 0)) {
189
if (resizingY && move1(point, y, 1)) {
194
// Final redraw and notify listeners.
197
event_({type: "brush", mode: dragging ? "move" : "resize"});
201
function move1(point, scale, i) {
202
var range = d3_scaleRange(scale),
205
position = origin[i],
206
size = extent[1][i] - extent[0][i],
210
// When dragging, reduce the range by the extent size and position.
213
r1 -= size + position;
216
// Clamp the point so that the extent fits within the range extent.
217
min = Math.max(r0, Math.min(r1, point[i]));
219
// Compute the new extent bounds.
221
max = (min += position) + size;
224
// If the ALT key is pressed, then preserve the center of the extent.
225
if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min));
227
// Compute the min and max of the position and point.
228
if (position < min) {
236
// Update the stored bounds.
237
if (extent[0][i] !== min || extent[1][i] !== max) {
245
function brushend() {
248
// reset the cursor styles
249
g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null);
250
d3.select("body").style("cursor", null);
252
w .on("mousemove.brush", null)
253
.on("mouseup.brush", null)
254
.on("touchmove.brush", null)
255
.on("touchend.brush", null)
256
.on("keydown.brush", null)
257
.on("keyup.brush", null);
259
event_({type: "brushend"});
264
brush.x = function(z) {
265
if (!arguments.length) return x;
267
resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore!
271
brush.y = function(z) {
272
if (!arguments.length) return y;
274
resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore!
278
brush.extent = function(z) {
279
var x0, x1, y0, y1, t;
281
// Invert the pixel extent to data-space.
282
if (!arguments.length) {
283
z = extentDomain || extent;
285
x0 = z[0][0], x1 = z[1][0];
287
x0 = extent[0][0], x1 = extent[1][0];
288
if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
289
if (x1 < x0) t = x0, x0 = x1, x1 = t;
293
y0 = z[0][1], y1 = z[1][1];
295
y0 = extent[0][1], y1 = extent[1][1];
296
if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
297
if (y1 < y0) t = y0, y0 = y1, y1 = t;
300
return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1];
303
// Scale the data-space extent to pixels.
304
extentDomain = [[0, 0], [0, 0]];
306
x0 = z[0], x1 = z[1];
307
if (y) x0 = x0[0], x1 = x1[0];
308
extentDomain[0][0] = x0, extentDomain[1][0] = x1;
309
if (x.invert) x0 = x(x0), x1 = x(x1);
310
if (x1 < x0) t = x0, x0 = x1, x1 = t;
311
extent[0][0] = x0 | 0, extent[1][0] = x1 | 0;
314
y0 = z[0], y1 = z[1];
315
if (x) y0 = y0[1], y1 = y1[1];
316
extentDomain[0][1] = y0, extentDomain[1][1] = y1;
317
if (y.invert) y0 = y(y0), y1 = y(y1);
318
if (y1 < y0) t = y0, y0 = y1, y1 = t;
319
extent[0][1] = y0 | 0, extent[1][1] = y1 | 0;
325
brush.clear = function() {
334
brush.empty = function() {
335
return (x && extent[0][0] === extent[1][0])
336
|| (y && extent[0][1] === extent[1][1]);
339
return d3.rebind(brush, event, "on");
342
var d3_svg_brushCursor = {
353
var d3_svg_brushResizes = [
354
["n", "e", "s", "w", "nw", "ne", "se", "sw"],