import “../core/document”; import “../core/rebind”; import “../event/dispatch”; import “../event/drag”; import “../event/event”; import “../event/mouse”; import “../event/touches”; import “../scale/scale”; import “../selection/selection”; import “svg”;
d3.svg.brush = function() {
var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, // x-scale, optional y = null, // y-scale, optional resizes = d3_svg_brushResizes[0], extent = [[0, 0], [0, 0]], // [x0, y0], [x1, y1], in pixels (integers) clamp = [true, true], // whether or not to clamp the extent to the range extentDomain; // the extent in data space, lazily created function brush(g) { g.each(function() { var g = d3.select(this), bg = g.selectAll(".background").data([0]), fg = g.selectAll(".extent").data([0]), tz = g.selectAll(".resize").data(resizes, String), e; // Prepare the brush container for events. g .style("pointer-events", "all") .on("mousedown.brush", brushstart) .on("touchstart.brush", brushstart); // An invisible, mouseable area for starting a new brush. bg.enter().append("rect") .attr("class", "background") .style("visibility", "hidden") .style("cursor", "crosshair"); // The visible brush extent; style this as you like! fg.enter().append("rect") .attr("class", "extent") .style("cursor", "move"); // More invisible rects for resizing the extent. tz.enter().append("g") .attr("class", function(d) { return "resize " + d; }) .style("cursor", function(d) { return d3_svg_brushCursor[d]; }) .append("rect") .attr("x", function(d) { return /[ew]$/.test(d) ? -3 : null; }) .attr("y", function(d) { return /^[ns]/.test(d) ? -3 : null; }) .attr("width", 6) .attr("height", 6) .style("visibility", "hidden"); // Show or hide the resizers. tz.style("display", brush.empty() ? "none" : null); // 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_scaleRange(x); bg.attr("x", e[0]).attr("width", e[1] - e[0]); redrawX(g); } if (y) { e = d3_scaleRange(y); bg.attr("y", e[0]).attr("height", e[1] - e[0]); redrawY(g); } redraw(g); }); } function redraw(g) { g.selectAll(".resize").attr("transform", function(d) { return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")"; }); } function redrawX(g) { g.select(".extent").attr("x", extent[0][0]); g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]); } function redrawY(g) { g.select(".extent").attr("y", extent[0][1]); g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]); } function brushstart() { var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(), center, origin = mouse(), offset; var w = d3.select(d3_window) .on("keydown.brush", keydown) .on("keyup.brush", keyup); if (d3.event.changedTouches) { w.on("touchmove.brush", brushmove).on("touchend.brush", brushend); } else { w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend); } // If the extent was clicked on, drag rather than brush; // store the point between the mouse and extent origin instead. if (dragging) { origin[0] = extent[0][0] - origin[0]; origin[1] = extent[0][1] - origin[1]; } // If a resizer was clicked on, record which side is to be resized. // Also, set the origin to the opposite side. else if (resizing) { var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); offset = [extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1]]; origin[0] = extent[ex][0]; origin[1] = extent[ey][1]; } // If the ALT key is down when starting a brush, the center is at the mouse. else if (d3.event.altKey) center = origin.slice(); // Propagate the active cursor to the body for the drag duration. g.style("pointer-events", "none").selectAll(".resize").style("display", null); d3.select("body").style("cursor", eventTarget.style("cursor")); // Notify listeners. event_({type: "brushstart"}); brushmove(); function mouse() { var touches = d3.event.changedTouches; return touches ? d3.touches(target, touches)[0] : d3.mouse(target); } function keydown() { if (d3.event.keyCode == 32) { if (!dragging) { center = null; origin[0] -= extent[1][0]; origin[1] -= extent[1][1]; dragging = 2; } d3_eventPreventDefault(); } } function keyup() { if (d3.event.keyCode == 32 && dragging == 2) { origin[0] += extent[1][0]; origin[1] += extent[1][1]; dragging = 0; d3_eventPreventDefault(); } } function brushmove() { var point = mouse(), moved = false; // Preserve the offset for thick resizers. if (offset) { point[0] += offset[0]; point[1] += offset[1]; } if (!dragging) { // If needed, determine the center from the current extent. if (d3.event.altKey) { if (!center) center = [(extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2]; // Update the origin, for when the ALT key is released. origin[0] = extent[+(point[0] < center[0])][0]; origin[1] = extent[+(point[1] < center[1])][1]; } // When the ALT key is released, we clear the center. else center = null; } // Update the brush extent for each dimension. if (resizingX && move1(point, x, 0)) { redrawX(g); moved = true; } if (resizingY && move1(point, y, 1)) { redrawY(g); moved = true; } // Final redraw and notify listeners. if (moved) { redraw(g); event_({type: "brush", mode: dragging ? "move" : "resize"}); } } function move1(point, scale, i) { var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], size = extent[1][i] - extent[0][i], min, max; // When dragging, reduce the range by the extent size and position. if (dragging) { r0 -= position; r1 -= size + position; } // Clamp the point (unless clamp set to false) so that the extent fits within the range extent. min = clamp[i] ? Math.max(r0, Math.min(r1, point[i])) : point[i]; // Compute the new extent bounds. if (dragging) { max = (min += position) + size; } else { // If the ALT key is pressed, then preserve the center of the extent. if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); // Compute the min and max of the position and point. if (position < min) { max = min; min = position; } else { max = position; } } // Update the stored bounds. if (extent[0][i] !== min || extent[1][i] !== max) { extentDomain = null; extent[0][i] = min; extent[1][i] = max; return true; } } function brushend() { brushmove(); // reset the cursor styles g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); d3.select("body").style("cursor", null); w .on("mousemove.brush", null) .on("mouseup.brush", null) .on("touchmove.brush", null) .on("touchend.brush", null) .on("keydown.brush", null) .on("keyup.brush", null); dragRestore(); event_({type: "brushend"}); } } brush.x = function(z) { if (!arguments.length) return x; x = z; resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore! return brush; }; brush.y = function(z) { if (!arguments.length) return y; y = z; resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore! return brush; }; brush.clamp = function(z) { if (!arguments.length) return x && y ? clamp : x || y ? clamp[+!x] : null; if (x && y) clamp = [!!z[0], !!z[1]]; else if (x || y) clamp[+!x] = !!z; return brush; }; brush.extent = function(z) { var x0, x1, y0, y1, t; // Invert the pixel extent to data-space. if (!arguments.length) { z = extentDomain || extent; if (x) { x0 = z[0][0], x1 = z[1][0]; if (!extentDomain) { x0 = extent[0][0], x1 = extent[1][0]; if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); if (x1 < x0) t = x0, x0 = x1, x1 = t; } } if (y) { y0 = z[0][1], y1 = z[1][1]; if (!extentDomain) { y0 = extent[0][1], y1 = extent[1][1]; if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); 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. extentDomain = [[0, 0], [0, 0]]; if (x) { x0 = z[0], x1 = z[1]; if (y) x0 = x0[0], x1 = x1[0]; extentDomain[0][0] = x0, extentDomain[1][0] = x1; if (x.invert) x0 = x(x0), x1 = x(x1); if (x1 < x0) t = x0, x0 = x1, x1 = t; extent[0][0] = x0 | 0, extent[1][0] = x1 | 0; } if (y) { y0 = z[0], y1 = z[1]; if (x) y0 = y0[1], y1 = y1[1]; extentDomain[0][1] = y0, extentDomain[1][1] = y1; if (y.invert) y0 = y(y0), y1 = y(y1); if (y1 < y0) t = y0, y0 = y1, y1 = t; extent[0][1] = y0 | 0, extent[1][1] = y1 | 0; } return brush; }; brush.clear = function() { extentDomain = null; 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]); }; return d3.rebind(brush, event, "on");
};
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"
};
var d3_svg_brushResizes = [
["n", "e", "s", "w", "nw", "ne", "se", "sw"], ["e", "w"], ["n", "s"], []
];