/*global d3,Raphael,$*/ /*! * Brush的兼容定义 */ ;(function (name, definition) { if (typeof define === 'function') { // Module define(definition); } else { // Assign to common namespaces or simply the global object (window) this[name] = definition(function (id) { return this[id];}); } })('Brush', function (require) { 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, d3_svg_brushEls; /** * set foreground and resizers' x and width; */ function d3_svg_brushRedrawX(brushEls, extent) { brushEls.fg.attr({"x": extent[0][0], "width": extent[1][0] - extent[0][0] }); brushEls.resizerSet.forEach(function (el) { var orient = el.data("resizeOrient"); if (orient === "n" || orient === "s" || orient === "w" || orient === "nw" || orient === "sw") { el.attr({"x": extent[0][0] - 2}); } else { // "e" "ne" "se" el.attr({"x": extent[1][0] - 2}); } if (orient === "n" || orient === "s") { el.attr({"width": extent[1][0] - extent[0][0]}); } }); } /** * set foreground and resizers' y and height; */ function d3_svg_brushRedrawY(brushEls, extent) { brushEls.fg.attr({"y": extent[0][1], "height": extent[1][1] - extent[0][1] }); brushEls.resizerSet.forEach(function (el) { var orient = el.data("resizeOrient"); if (orient === "n" || orient === "e" || orient === "w" || orient === "nw" || orient === "ne") { el.attr({"y": extent[0][1] - 3}); } else { // "s" "se" "sw" el.attr({"y": extent[1][1] - 4}); } if (orient === "e" || orient === "w") { el.attr({"height": extent[1][1] - extent[0][1]}); } }); } /** * function from d3, get scaleRange of an ordinal scale * @param domain, ordinal scale's range */ function d3_scaleExtent(domain) { var start = domain[0], stop = domain[domain.length - 1]; return start < stop ? [start, stop] : [stop, start]; } /** * function from d3, get scaleRange of a scale */ function d3_scaleRange(scale) { return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); } /** * function from d3, called by d3_svg_brushMove, compute new brush extent after brush moved */ function d3_svg_brushMove1(mouse, scale, i) { var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], 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) { r0 -= offset; r1 -= size + offset; } // Clamp the mouse so that the extent fits within the range extent. min = Math.max(r0, Math.min(r1, 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(r0, Math.min(r1, 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 from d3, after brush moved, compute new brush extent * and redraw foreground and resizer. */ function d3_svg_brushMove(e) { if (d3_svg_brushOffset) { var bgOffset = $(d3_svg_brushTarget).offset(); var mouse = [e.pageX - bgOffset.left, e.pageY - bgOffset.top]; if (!d3_svg_brushDrag) { // If needed, determine the center from the current extent. if (e.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]; } else { // When the ALT key is released, we clear the center. 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(d3_svg_brushEls, d3_svg_brushExtent); } if (d3_svg_brushY) { d3_svg_brushMove1(mouse, d3_svg_brushY, 1); d3_svg_brushRedrawY(d3_svg_brushEls, d3_svg_brushExtent); } // Notify listeners. d3_svg_brushDispatch("brush"); } } /** * function from d3, * reset brush offset if user presses "space" key while brushing a new area, * to ensure foreground's size unchanged while position changing. */ function d3_svg_brushKeydown(e) { if (e.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; e.stopPropagation(); } } /** * function from d3, * reset brush offset if "space" key up to restore normal drush state. */ function d3_svg_brushKeyup(e) { if (e.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; e.stopPropagation(); } } /** * function from d3, * mouse up and stop brushing. */ function d3_svg_brushUp(e) { if (d3_svg_brushOffset) { d3_svg_brushMove(e); d3_svg_brushEls.resizerSet.forEach(function (resizer) { //adjust all resizers var orient = resizer.data("resizeOrient"); var size = d3_svg_brush.empty() ? 0 : 6; if (orient === "n" || orient === "s") { resizer.attr({"height": size}); } else { resizer.attr({"width": size}); } }); 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 = d3_svg_brushEls = null; e.stopPropagation(); } } 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 vml_brushCursor = { n: "row-resize", e: "col-resize", s: "row-resize", w: "col-resize", nw: "all-scroll", ne: "all-scroll", se: "all-scroll", sw: "all-scroll" }; var 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] e, left, top, width, height, backgroundAttr = { "fill": "#dddddd", "stroke": "none", "cursor": "crosshair" }, foregroundAttr = { "fill": "steelblue", "stroke": "none", "cursor": "move" }, brushStart = function () {}, brushing = function () {}, brushEnd = function () {}, brushEls = {}, brushClass; /*! * mouse down and start brushing or dragging. */ function down(e) { var target = e.target, bgOffset; // Store some global state for the duration of the brush gesture. d3_svg_brush = brush; d3_svg_brushTarget = $(brushEls.paper.canvas).parent(); d3_svg_brushExtent = extent; bgOffset = $(d3_svg_brushTarget).offset(); d3_svg_brushOffset = [e.pageX - bgOffset.left, e.pageY - bgOffset.top]; d3_svg_brushEls = brushEls; // If the extent was clicked on, drag rather than brush; // store the offset between the mouse and extent origin instead. d3_svg_brushDrag = target.__brushNodeType__ === "fg" ? true : false; if (d3_svg_brushDrag) { d3_svg_brushOffset[0] = extent[0][0] - d3_svg_brushOffset[0]; d3_svg_brushOffset[1] = extent[0][1] - d3_svg_brushOffset[1]; } else if (/^resize/.test(target.__brushNodeType__)) { // If a resizer was clicked on, record which side is to be resized. // Also, set the offset to the opposite side. d3_svg_brushResize = target.__brushNodeType__.split("_")[1]; d3_svg_brushOffset[0] = extent[+(/w$/.test(d3_svg_brushResize))][0]; d3_svg_brushOffset[1] = extent[+(/^n/.test(d3_svg_brushResize))][1]; } else if (e.altKey) { // If the ALT key is down when starting a brush, the center is at the mouse. 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(e); e.stopPropagation(); } /*! * create brush * input a Raphael paper, return a brush object. */ function brush(paper) { var resizes = x && y ? ["n", "e", "s", "w", "nw", "ne", "se", "sw"] : x ? ["e", "w"] : y ? ["n", "s"] : []; if (x) { e = d3_scaleRange(x); left = e[0]; width = e[1] - e[0]; } if (y) { e = d3_scaleRange(y); top = e[0]; height = e[1] - e[0]; } brushEls.paper = paper; brushEls.brushSet = paper.set(); brushEls.resizerSet = paper.set(); brushEls.bg = paper.rect(left, top, width, height) .attr({"fill": "#dddddd", "stroke": "none", "cursor": "crosshair" }) .attr(backgroundAttr); brushEls.bg.node.__brushNodeType__ = "bg"; brushEls.bg.node.ondragstart = function () { return false; };//firefox drag bug fix; brushClass = "brush" + brushEls.bg.id; //$(brushEls.bg.node).addClass("brush bg rvml"); // fail to svg brushEls.bg.node.setAttribute("class", "brush bg rvml " + brushClass); brushEls.bg.node.setAttribute("className", "brush bg rvml " + brushClass);// IE 6,7 brushEls.fg = paper.rect(left, top, (x ? 0 : width), (y ? 0 : height)) .attr({"fill": "steelblue", "stroke": "none", "cursor": "move" }) .attr(foregroundAttr); brushEls.fg.node.__brushNodeType__ = "fg"; brushEls.fg.node.ondragstart = function () { return false; };//firefox drag bug fix; //$(brushEls.fg.node).addClass("brush fg rvml"); //fail to svg brushEls.fg.node.setAttribute("class", "brush fg rvml " + brushClass); brushEls.fg.node.setAttribute("className", "brush fg rvml " + brushClass);// IE 6,7 resizes.forEach(function (d) { var resizer = paper.rect(left, top, (x ? 6 : width), (y ? 6 : height)) .data("resizeOrient", d) .attr({"cursor": d3_svg_brushCursor[d], "fill": "white", "stroke": "black", "opacity": 0}); if (Raphael.vml) { resizer.attr({"cursor": vml_brushCursor[d]}); } if (brush.empty()) { //hide all resizers if (d === "n" || d === "s") { resizer.attr({"height": 0}); } else { resizer.attr({"width": 0}); } } resizer.node.__brushNodeType__ = "resizer_" + d; resizer.node.ondragstart = function () { return false; };//firefox drag bug fix; //$(resizer.node).addClass("brush rvml " + d3_svg_brushCursor[d]); //fail to svg resizer.node.setAttribute("class", "brush rvml " + brushClass + " " + d3_svg_brushCursor[d]); //IE 6,7 resizer.node.setAttribute("className", "brush rvml " + brushClass + " " + d3_svg_brushCursor[d]); brushEls.resizerSet.push(resizer); }); if (x) { d3_svg_brushRedrawX(brushEls, extent); } if (y) { d3_svg_brushRedrawY(brushEls, extent); } //$(paper.canvas).delegate(".brush","mousedown", down); //$(paper.canvas).undelegate(".brush","mousedown", down); //$(paper.canvas).delegate(".brush","mousedown", down); //$(paper.canvas).off("mousedown", ".brush", down); $(paper.canvas).on("mousedown", "." + brushClass, down); brush.brushElements = brushEls; return brush; } // dispatch event, bind data to golbal variant d3.event. var dispatcher = function (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; } }; }; /*! * get or set brush's left * @param z, a value in brush scale's domain */ brush.left = function (z) { if (!arguments.length) { return left; } left = z; return brush; }; /*! * get or set brush's top * @param z, a value in brush scale's domain */ brush.top = function (z) { if (!arguments.length) { return top; } top = z; return brush; }; /*! * get or set brush's width * @param z, a value in brush scale's domain */ brush.width = function (z) { if (!arguments.length) { return width; } width = z; return brush; }; /*! * get or set brush's height * @param z, a value in brush scale's domain */ brush.height = function (z) { if (!arguments.length) { return height; } height = z; return brush; }; /*! * get or set brush's x scale * @param z, d3's sacle object */ brush.x = function (z) { if (!arguments.length) { return x; } x = z; return brush; }; /*! * get or set brush's y scale * @param z, d3's sacle object */ brush.y = function (z) { if (!arguments.length) { return y; } y = z; return brush; }; /*! * get or set brush's extent in scale's domain format. * if both x and y exist, @param z's format is [[x0, y0], [x1, y1]] * if only one of x and y exists, @param z's format is [x0, x1] or [y0, y1]. */ brush.extent = function (z) { var x0, x1, y0, y1, t; // Invert the pixel extent to data-space. if (!arguments.length) { if (x) { 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 = 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. if (x) { x0 = z[0]; x1 = z[1]; if (y) { x0 = x0[0]; x1 = x1[0]; } if (x.invert) { 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]; } if (y.invert) { 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; }; //empty extent and refresh foreground brush.clear = function () { extent[0][0] = extent[0][1] = extent[1][0] = extent[1][1] = 0; brush.refresh(); return brush; }; //refresh foreground brush.refresh = function () { if (x) { d3_svg_brushRedrawX(brushEls, extent); } if (y) { d3_svg_brushRedrawY(brushEls, extent); } return brush; }; //remove all brush elements, so users can reset brush attributes and redraw it. brush.remove = function () { $(paper.canvas).off("mousedown", "." + brushClass, down); brushEls.fg.remove(); brushEls.bg.remove(); brushEls.resizerSet.remove(); return brush; }; // if brush is empty, return true, else false; brush.empty = function () { return (x && extent[0][0] === extent[1][0]) || (y && extent[0][1] === extent[1][1]); }; // set background attribute. brush.backgroundAttr = function (x) { if (!arguments.length) { return backgroundAttr; } backgroundAttr = x; return brush; }; // set foreground attribute. brush.foregroundAttr = function (x) { if (!arguments.length) { return foregroundAttr; } foregroundAttr = x; return brush; }; $(document).bind("mousemove", d3_svg_brushMove) .bind("mouseup", d3_svg_brushUp) .bind("keydown", d3_svg_brushKeydown) .bind("keyup", d3_svg_brushKeyup); return d3.rebind(brush, event, "on"); }; return Brush; });