toolkit/javascript/d3/src/svg/brush.js
changeset 47 c0b4a8b5a012
equal deleted inserted replaced
46:efd9c589177a 47:c0b4a8b5a012
       
     1 d3.svg.brush = function() {
       
     2   var event = d3.dispatch("brushstart", "brush", "brushend"),
       
     3       x, // x-scale, optional
       
     4       y, // y-scale, optional
       
     5       extent = [[0, 0], [0, 0]]; // [x0, y0], [x1, y1]
       
     6 
       
     7   function brush(g) {
       
     8     var resizes = x && y ? ["n", "e", "s", "w", "nw", "ne", "se", "sw"]
       
     9         : x ? ["e", "w"]
       
    10         : y ? ["n", "s"]
       
    11         : [];
       
    12 
       
    13     g.each(function() {
       
    14       var g = d3.select(this).on("mousedown.brush", down),
       
    15           bg = g.selectAll(".background").data([,]),
       
    16           fg = g.selectAll(".extent").data([,]),
       
    17           tz = g.selectAll(".resize").data(resizes, String),
       
    18           e;
       
    19 
       
    20       // An invisible, mouseable area for starting a new brush.
       
    21       bg.enter().append("svg:rect")
       
    22           .attr("class", "background")
       
    23           .style("visibility", "hidden")
       
    24           .style("pointer-events", "all")
       
    25           .style("cursor", "crosshair");
       
    26 
       
    27       // The visible brush extent; style this as you like!
       
    28       fg.enter().append("svg:rect")
       
    29           .attr("class", "extent")
       
    30           .style("cursor", "move");
       
    31 
       
    32       // More invisible rects for resizing the extent.
       
    33       tz.enter().append("svg:rect")
       
    34           .attr("class", function(d) { return "resize " + d; })
       
    35           .attr("width", 6)
       
    36           .attr("height", 6)
       
    37           .style("visibility", "hidden")
       
    38           .style("pointer-events", brush.empty() ? "none" : "all")
       
    39           .style("cursor", function(d) { return d3_svg_brushCursor[d]; });
       
    40 
       
    41       // Remove any superfluous resizers.
       
    42       tz.exit().remove();
       
    43 
       
    44       // Initialize the background to fill the defined range.
       
    45       // If the range isn't defined, you can post-process.
       
    46       if (x) {
       
    47         e = d3_scaleExtent(x.range());
       
    48         bg.attr("x", e[0]).attr("width", e[1] - e[0]);
       
    49         d3_svg_brushRedrawX(g, extent);
       
    50       }
       
    51       if (y) {
       
    52         e = d3_scaleExtent(y.range());
       
    53         bg.attr("y", e[0]).attr("height", e[1] - e[0]);
       
    54         d3_svg_brushRedrawY(g, extent);
       
    55       }
       
    56     });
       
    57   }
       
    58 
       
    59   function down() {
       
    60     var target = d3.select(d3.event.target);
       
    61 
       
    62     // Store some global state for the duration of the brush gesture.
       
    63     d3_svg_brush = brush;
       
    64     d3_svg_brushTarget = this;
       
    65     d3_svg_brushExtent = extent;
       
    66     d3_svg_brushOffset = d3.svg.mouse(d3_svg_brushTarget);
       
    67 
       
    68     // If the extent was clicked on, drag rather than brush;
       
    69     // store the offset between the mouse and extent origin instead.
       
    70     if (d3_svg_brushDrag = target.classed("extent")) {
       
    71       d3_svg_brushOffset[0] = extent[0][0] - d3_svg_brushOffset[0];
       
    72       d3_svg_brushOffset[1] = extent[0][1] - d3_svg_brushOffset[1];
       
    73     }
       
    74 
       
    75     // If a resizer was clicked on, record which side is to be resized.
       
    76     // Also, set the offset to the opposite side.
       
    77     else if (target.classed("resize")) {
       
    78       d3_svg_brushResize = d3.event.target.__data__;
       
    79       d3_svg_brushOffset[0] = extent[+/w$/.test(d3_svg_brushResize)][0];
       
    80       d3_svg_brushOffset[1] = extent[+/^n/.test(d3_svg_brushResize)][1];
       
    81     }
       
    82 
       
    83     // If the ALT key is down when starting a brush, the center is at the mouse.
       
    84     else if (d3.event.altKey) {
       
    85       d3_svg_brushCenter = d3_svg_brushOffset.slice();
       
    86     }
       
    87 
       
    88     // Restrict which dimensions are resized.
       
    89     d3_svg_brushX = !/^(n|s)$/.test(d3_svg_brushResize) && x;
       
    90     d3_svg_brushY = !/^(e|w)$/.test(d3_svg_brushResize) && y;
       
    91 
       
    92     // Notify listeners.
       
    93     d3_svg_brushDispatch = dispatcher(this, arguments);
       
    94     d3_svg_brushDispatch("brushstart");
       
    95     d3_svg_brushMove();
       
    96     d3_eventCancel();
       
    97   }
       
    98 
       
    99   function dispatcher(that, argumentz) {
       
   100     return function(type) {
       
   101       var e = d3.event;
       
   102       try {
       
   103         d3.event = {type: type, target: brush};
       
   104         event[type].apply(that, argumentz);
       
   105       } finally {
       
   106         d3.event = e;
       
   107       }
       
   108     };
       
   109   }
       
   110 
       
   111   brush.x = function(z) {
       
   112     if (!arguments.length) return x;
       
   113     x = z;
       
   114     return brush;
       
   115   };
       
   116 
       
   117   brush.y = function(z) {
       
   118     if (!arguments.length) return y;
       
   119     y = z;
       
   120     return brush;
       
   121   };
       
   122 
       
   123   brush.extent = function(z) {
       
   124     var x0, x1, y0, y1, t;
       
   125 
       
   126     // Invert the pixel extent to data-space.
       
   127     if (!arguments.length) {
       
   128       if (x) {
       
   129         x0 = x.invert(extent[0][0]), x1 = x.invert(extent[1][0]);
       
   130         if (x1 < x0) t = x0, x0 = x1, x1 = t;
       
   131       }
       
   132       if (y) {
       
   133         y0 = y.invert(extent[0][1]), y1 = y.invert(extent[1][1]);
       
   134         if (y1 < y0) t = y0, y0 = y1, y1 = t;
       
   135       }
       
   136       return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1];
       
   137     }
       
   138 
       
   139     // Scale the data-space extent to pixels.
       
   140     if (x) {
       
   141       x0 = z[0], x1 = z[1];
       
   142       if (y) x0 = x0[0], x1 = x1[0];
       
   143       x0 = x(x0), x1 = x(x1);
       
   144       if (x1 < x0) t = x0, x0 = x1, x1 = t;
       
   145       extent[0][0] = x0, extent[1][0] = x1;
       
   146     }
       
   147     if (y) {
       
   148       y0 = z[0], y1 = z[1];
       
   149       if (x) y0 = y0[1], y1 = y1[1];
       
   150       y0 = y(y0), y1 = y(y1);
       
   151       if (y1 < y0) t = y0, y0 = y1, y1 = t;
       
   152       extent[0][1] = y0, extent[1][1] = y1;
       
   153     }
       
   154 
       
   155     return brush;
       
   156   };
       
   157 
       
   158   brush.clear = function() {
       
   159     extent[0][0] =
       
   160     extent[0][1] =
       
   161     extent[1][0] =
       
   162     extent[1][1] = 0;
       
   163     return brush;
       
   164   };
       
   165 
       
   166   brush.empty = function() {
       
   167     return (x && extent[0][0] === extent[1][0])
       
   168         || (y && extent[0][1] === extent[1][1]);
       
   169   };
       
   170 
       
   171   brush.on = function(type, listener) {
       
   172     event.on(type, listener);
       
   173     return brush;
       
   174   };
       
   175 
       
   176   d3.select(window)
       
   177       .on("mousemove.brush", d3_svg_brushMove)
       
   178       .on("mouseup.brush", d3_svg_brushUp)
       
   179       .on("keydown.brush", d3_svg_brushKeydown)
       
   180       .on("keyup.brush", d3_svg_brushKeyup);
       
   181 
       
   182   return brush;
       
   183 };
       
   184 
       
   185 var d3_svg_brush,
       
   186     d3_svg_brushDispatch,
       
   187     d3_svg_brushTarget,
       
   188     d3_svg_brushX,
       
   189     d3_svg_brushY,
       
   190     d3_svg_brushExtent,
       
   191     d3_svg_brushDrag,
       
   192     d3_svg_brushResize,
       
   193     d3_svg_brushCenter,
       
   194     d3_svg_brushOffset;
       
   195 
       
   196 function d3_svg_brushRedrawX(g, extent) {
       
   197   g.select(".extent").attr("x", extent[0][0]);
       
   198   g.selectAll(".n,.s,.w,.nw,.sw").attr("x", extent[0][0] - 2);
       
   199   g.selectAll(".e,.ne,.se").attr("x", extent[1][0] - 3);
       
   200   g.selectAll(".extent,.n,.s").attr("width", extent[1][0] - extent[0][0]);
       
   201 }
       
   202 
       
   203 function d3_svg_brushRedrawY(g, extent) {
       
   204   g.select(".extent").attr("y", extent[0][1]);
       
   205   g.selectAll(".n,.e,.w,.nw,.ne").attr("y", extent[0][1] - 3);
       
   206   g.selectAll(".s,.se,.sw").attr("y", extent[1][1] - 4);
       
   207   g.selectAll(".extent,.e,.w").attr("height", extent[1][1] - extent[0][1]);
       
   208 }
       
   209 
       
   210 function d3_svg_brushKeydown() {
       
   211   if (d3.event.keyCode == 32 && d3_svg_brushTarget && !d3_svg_brushDrag) {
       
   212     d3_svg_brushCenter = null;
       
   213     d3_svg_brushOffset[0] -= d3_svg_brushExtent[1][0];
       
   214     d3_svg_brushOffset[1] -= d3_svg_brushExtent[1][1];
       
   215     d3_svg_brushDrag = 2;
       
   216     d3_eventCancel();
       
   217   }
       
   218 }
       
   219 
       
   220 function d3_svg_brushKeyup() {
       
   221   if (d3.event.keyCode == 32 && d3_svg_brushDrag == 2) {
       
   222     d3_svg_brushOffset[0] += d3_svg_brushExtent[1][0];
       
   223     d3_svg_brushOffset[1] += d3_svg_brushExtent[1][1];
       
   224     d3_svg_brushDrag = 0;
       
   225     d3_eventCancel();
       
   226   }
       
   227 }
       
   228 
       
   229 function d3_svg_brushMove() {
       
   230   if (d3_svg_brushOffset) {
       
   231     var mouse = d3.svg.mouse(d3_svg_brushTarget),
       
   232         g = d3.select(d3_svg_brushTarget);
       
   233 
       
   234     if (!d3_svg_brushDrag) {
       
   235 
       
   236       // If needed, determine the center from the current extent.
       
   237       if (d3.event.altKey) {
       
   238         if (!d3_svg_brushCenter) {
       
   239           d3_svg_brushCenter = [
       
   240             (d3_svg_brushExtent[0][0] + d3_svg_brushExtent[1][0]) / 2,
       
   241             (d3_svg_brushExtent[0][1] + d3_svg_brushExtent[1][1]) / 2
       
   242           ];
       
   243         }
       
   244 
       
   245         // Update the offset, for when the ALT key is released.
       
   246         d3_svg_brushOffset[0] = d3_svg_brushExtent[+(mouse[0] < d3_svg_brushCenter[0])][0];
       
   247         d3_svg_brushOffset[1] = d3_svg_brushExtent[+(mouse[1] < d3_svg_brushCenter[1])][1];
       
   248       }
       
   249 
       
   250       // When the ALT key is released, we clear the center.
       
   251       else d3_svg_brushCenter = null;
       
   252     }
       
   253 
       
   254     // Update the brush extent for each dimension.
       
   255     if (d3_svg_brushX) {
       
   256       d3_svg_brushMove1(mouse, d3_svg_brushX, 0);
       
   257       d3_svg_brushRedrawX(g, d3_svg_brushExtent);
       
   258     }
       
   259     if (d3_svg_brushY) {
       
   260       d3_svg_brushMove1(mouse, d3_svg_brushY, 1);
       
   261       d3_svg_brushRedrawY(g, d3_svg_brushExtent);
       
   262     }
       
   263 
       
   264     // Notify listeners.
       
   265     d3_svg_brushDispatch("brush");
       
   266   }
       
   267 }
       
   268 
       
   269 function d3_svg_brushMove1(mouse, scale, i) {
       
   270   var range = d3_scaleExtent(scale.range()),
       
   271       offset = d3_svg_brushOffset[i],
       
   272       size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i],
       
   273       min,
       
   274       max;
       
   275 
       
   276   // When dragging, reduce the range by the extent size and offset.
       
   277   if (d3_svg_brushDrag) {
       
   278     range[0] -= offset;
       
   279     range[1] -= size + offset;
       
   280   }
       
   281 
       
   282   // Clamp the mouse so that the extent fits within the range extent.
       
   283   min = Math.max(range[0], Math.min(range[1], mouse[i]));
       
   284 
       
   285   // Compute the new extent bounds.
       
   286   if (d3_svg_brushDrag) {
       
   287     max = (min += offset) + size;
       
   288   } else {
       
   289 
       
   290     // If the ALT key is pressed, then preserve the center of the extent.
       
   291     if (d3_svg_brushCenter) offset = Math.max(range[0], Math.min(range[1], 2 * d3_svg_brushCenter[i] - min));
       
   292 
       
   293     // Compute the min and max of the offset and mouse.
       
   294     if (offset < min) {
       
   295       max = min;
       
   296       min = offset;
       
   297     } else {
       
   298       max = offset;
       
   299     }
       
   300   }
       
   301 
       
   302   // Update the stored bounds.
       
   303   d3_svg_brushExtent[0][i] = min;
       
   304   d3_svg_brushExtent[1][i] = max;
       
   305 }
       
   306 
       
   307 function d3_svg_brushUp() {
       
   308   if (d3_svg_brushOffset) {
       
   309     d3_svg_brushMove();
       
   310     d3.select(d3_svg_brushTarget).selectAll(".resize").style("pointer-events", d3_svg_brush.empty() ? "none" : "all");
       
   311     d3_svg_brushDispatch("brushend");
       
   312     d3_svg_brush =
       
   313     d3_svg_brushDispatch =
       
   314     d3_svg_brushTarget =
       
   315     d3_svg_brushX =
       
   316     d3_svg_brushY =
       
   317     d3_svg_brushExtent =
       
   318     d3_svg_brushDrag =
       
   319     d3_svg_brushResize =
       
   320     d3_svg_brushCenter =
       
   321     d3_svg_brushOffset = null;
       
   322     d3_eventCancel();
       
   323   }
       
   324 }
       
   325 
       
   326 var d3_svg_brushCursor = {
       
   327   n: "ns-resize",
       
   328   e: "ew-resize",
       
   329   s: "ns-resize",
       
   330   w: "ew-resize",
       
   331   nw: "nwse-resize",
       
   332   ne: "nesw-resize",
       
   333   se: "nwse-resize",
       
   334   sw: "nesw-resize"
       
   335 };