d3.svg.brush = function() {
var event = d3.dispatch("brushstart", "brush", "brushend"),
x, // x-scale, optional
y, // y-scale, optional
extent = [[0, 0], [0, 0]]; // [x0, y0], [x1, y1]
function brush(g) {
var resizes = x && y ? ["n", "e", "s", "w", "nw", "ne", "se", "sw"]
: x ? ["e", "w"]
: y ? ["n", "s"]
: [];
g.each(function() {
var g = d3.select(this).on("mousedown.brush", down),
bg = g.selectAll(".background").data([,]),
fg = g.selectAll(".extent").data([,]),
tz = g.selectAll(".resize").data(resizes, String),
e;
// An invisible, mouseable area for starting a new brush.
bg.enter().append("svg:rect")
.attr("class", "background")
.style("visibility", "hidden")
.style("pointer-events", "all")
.style("cursor", "crosshair");
// The visible brush extent; style this as you like!
fg.enter().append("svg:rect")
.attr("class", "extent")
.style("cursor", "move");
// More invisible rects for resizing the extent.
tz.enter().append("svg:rect")
.attr("class", function(d) { return "resize " + d; })
.attr("width", 6)
.attr("height", 6)
.style("visibility", "hidden")
.style("pointer-events", brush.empty() ? "none" : "all")
.style("cursor", function(d) { return d3_svg_brushCursor[d]; });
// Remove any superfluous resizers.
tz.exit().remove();
// Initialize the background to fill the defined range.
// If the range isn't defined, you can post-process.
if (x) {
e = d3_scaleExtent(x.range());
bg.attr("x", e[0]).attr("width", e[1] - e[0]);
d3_svg_brushRedrawX(g, extent);
}
if (y) {
e = d3_scaleExtent(y.range());
bg.attr("y", e[0]).attr("height", e[1] - e[0]);
d3_svg_brushRedrawY(g, extent);
}
});
}
function down() {
var target = d3.select(d3.event.target);
// Store some global state for the duration of the brush gesture.
d3_svg_brush = brush;
d3_svg_brushTarget = this;
d3_svg_brushExtent = extent;
d3_svg_brushOffset = d3.svg.mouse(d3_svg_brushTarget);
// If the extent was clicked on, drag rather than brush;
// store the offset between the mouse and extent origin instead.
if (d3_svg_brushDrag = target.classed("extent")) {
d3_svg_brushOffset[0] = extent[0][0] - d3_svg_brushOffset[0];
d3_svg_brushOffset[1] = extent[0][1] - d3_svg_brushOffset[1];
}
// If a resizer was clicked on, record which side is to be resized.
// Also, set the offset to the opposite side.
else if (target.classed("resize")) {
d3_svg_brushResize = d3.event.target.__data__;
d3_svg_brushOffset[0] = extent[+/w$/.test(d3_svg_brushResize)][0];
d3_svg_brushOffset[1] = extent[+/^n/.test(d3_svg_brushResize)][1];
}
// If the ALT key is down when starting a brush, the center is at the mouse.
else if (d3.event.altKey) {
d3_svg_brushCenter = d3_svg_brushOffset.slice();
}
// Restrict which dimensions are resized.
d3_svg_brushX = !/^(n|s)$/.test(d3_svg_brushResize) && x;
d3_svg_brushY = !/^(e|w)$/.test(d3_svg_brushResize) && y;
// Notify listeners.
d3_svg_brushDispatch = dispatcher(this, arguments);
d3_svg_brushDispatch("brushstart");
d3_svg_brushMove();
d3_eventCancel();
}
function dispatcher(that, argumentz) {
return function(type) {
var e = d3.event;
try {
d3.event = {type: type, target: brush};
event[type].apply(that, argumentz);
} finally {
d3.event = e;
}
};
}
brush.x = function(z) {
if (!arguments.length) return x;
x = z;
return brush;
};
brush.y = function(z) {
if (!arguments.length) return y;
y = z;
return brush;
};
brush.extent = function(z) {
var x0, x1, y0, y1, t;
// Invert the pixel extent to data-space.
if (!arguments.length) {
if (x) {
x0 = x.invert(extent[0][0]), x1 = x.invert(extent[1][0]);
if (x1 < x0) t = x0, x0 = x1, x1 = t;
}
if (y) {
y0 = y.invert(extent[0][1]), y1 = y.invert(extent[1][1]);
if (y1 < y0) t = y0, y0 = y1, y1 = t;
}
return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1];
}
// Scale the data-space extent to pixels.
if (x) {
x0 = z[0], x1 = z[1];
if (y) x0 = x0[0], x1 = x1[0];
x0 = x(x0), x1 = x(x1);
if (x1 < x0) t = x0, x0 = x1, x1 = t;
extent[0][0] = x0, extent[1][0] = x1;
}
if (y) {
y0 = z[0], y1 = z[1];
if (x) y0 = y0[1], y1 = y1[1];
y0 = y(y0), y1 = y(y1);
if (y1 < y0) t = y0, y0 = y1, y1 = t;
extent[0][1] = y0, extent[1][1] = y1;
}
return brush;
};
brush.clear = function() {
extent[0][0] =
extent[0][1] =
extent[1][0] =
extent[1][1] = 0;
return brush;
};
brush.empty = function() {
return (x && extent[0][0] === extent[1][0])
|| (y && extent[0][1] === extent[1][1]);
};
brush.on = function(type, listener) {
event.on(type, listener);
return brush;
};
d3.select(window)
.on("mousemove.brush", d3_svg_brushMove)
.on("mouseup.brush", d3_svg_brushUp)
.on("keydown.brush", d3_svg_brushKeydown)
.on("keyup.brush", d3_svg_brushKeyup);
return brush;
};
var d3_svg_brush,
d3_svg_brushDispatch,
d3_svg_brushTarget,
d3_svg_brushX,
d3_svg_brushY,
d3_svg_brushExtent,
d3_svg_brushDrag,
d3_svg_brushResize,
d3_svg_brushCenter,
d3_svg_brushOffset;
function d3_svg_brushRedrawX(g, extent) {
g.select(".extent").attr("x", extent[0][0]);
g.selectAll(".n,.s,.w,.nw,.sw").attr("x", extent[0][0] - 2);
g.selectAll(".e,.ne,.se").attr("x", extent[1][0] - 3);
g.selectAll(".extent,.n,.s").attr("width", extent[1][0] - extent[0][0]);
}
function d3_svg_brushRedrawY(g, extent) {
g.select(".extent").attr("y", extent[0][1]);
g.selectAll(".n,.e,.w,.nw,.ne").attr("y", extent[0][1] - 3);
g.selectAll(".s,.se,.sw").attr("y", extent[1][1] - 4);
g.selectAll(".extent,.e,.w").attr("height", extent[1][1] - extent[0][1]);
}
function d3_svg_brushKeydown() {
if (d3.event.keyCode == 32 && d3_svg_brushTarget && !d3_svg_brushDrag) {
d3_svg_brushCenter = null;
d3_svg_brushOffset[0] -= d3_svg_brushExtent[1][0];
d3_svg_brushOffset[1] -= d3_svg_brushExtent[1][1];
d3_svg_brushDrag = 2;
d3_eventCancel();
}
}
function d3_svg_brushKeyup() {
if (d3.event.keyCode == 32 && d3_svg_brushDrag == 2) {
d3_svg_brushOffset[0] += d3_svg_brushExtent[1][0];
d3_svg_brushOffset[1] += d3_svg_brushExtent[1][1];
d3_svg_brushDrag = 0;
d3_eventCancel();
}
}
function d3_svg_brushMove() {
if (d3_svg_brushOffset) {
var mouse = d3.svg.mouse(d3_svg_brushTarget),
g = d3.select(d3_svg_brushTarget);
if (!d3_svg_brushDrag) {
// If needed, determine the center from the current extent.
if (d3.event.altKey) {
if (!d3_svg_brushCenter) {
d3_svg_brushCenter = [
(d3_svg_brushExtent[0][0] + d3_svg_brushExtent[1][0]) / 2,
(d3_svg_brushExtent[0][1] + d3_svg_brushExtent[1][1]) / 2
];
}
// Update the offset, for when the ALT key is released.
d3_svg_brushOffset[0] = d3_svg_brushExtent[+(mouse[0] < d3_svg_brushCenter[0])][0];
d3_svg_brushOffset[1] = d3_svg_brushExtent[+(mouse[1] < d3_svg_brushCenter[1])][1];
}
// When the ALT key is released, we clear the center.
else d3_svg_brushCenter = null;
}
// Update the brush extent for each dimension.
if (d3_svg_brushX) {
d3_svg_brushMove1(mouse, d3_svg_brushX, 0);
d3_svg_brushRedrawX(g, d3_svg_brushExtent);
}
if (d3_svg_brushY) {
d3_svg_brushMove1(mouse, d3_svg_brushY, 1);
d3_svg_brushRedrawY(g, d3_svg_brushExtent);
}
// Notify listeners.
d3_svg_brushDispatch("brush");
}
}
function d3_svg_brushMove1(mouse, scale, i) {
var range = d3_scaleExtent(scale.range()),
offset = d3_svg_brushOffset[i],
size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i],
min,
max;
// When dragging, reduce the range by the extent size and offset.
if (d3_svg_brushDrag) {
range[0] -= offset;
range[1] -= size + offset;
}
// Clamp the mouse so that the extent fits within the range extent.
min = Math.max(range[0], Math.min(range[1], mouse[i]));
// Compute the new extent bounds.
if (d3_svg_brushDrag) {
max = (min += offset) + size;
} else {
// If the ALT key is pressed, then preserve the center of the extent.
if (d3_svg_brushCenter) offset = Math.max(range[0], Math.min(range[1], 2 * d3_svg_brushCenter[i] - min));
// Compute the min and max of the offset and mouse.
if (offset < min) {
max = min;
min = offset;
} else {
max = offset;
}
}
// Update the stored bounds.
d3_svg_brushExtent[0][i] = min;
d3_svg_brushExtent[1][i] = max;
}
function d3_svg_brushUp() {
if (d3_svg_brushOffset) {
d3_svg_brushMove();
d3.select(d3_svg_brushTarget).selectAll(".resize").style("pointer-events", d3_svg_brush.empty() ? "none" : "all");
d3_svg_brushDispatch("brushend");
d3_svg_brush =
d3_svg_brushDispatch =
d3_svg_brushTarget =
d3_svg_brushX =
d3_svg_brushY =
d3_svg_brushExtent =
d3_svg_brushDrag =
d3_svg_brushResize =
d3_svg_brushCenter =
d3_svg_brushOffset = null;
d3_eventCancel();
}
}
var d3_svg_brushCursor = {
n: "ns-resize",
e: "ew-resize",
s: "ns-resize",
w: "ew-resize",
nw: "nwse-resize",
ne: "nesw-resize",
se: "nwse-resize",
sw: "nesw-resize"
};