toolkit/javascript/d3/src/behavior/zoom.js
changeset 47 c0b4a8b5a012
equal deleted inserted replaced
46:efd9c589177a 47:c0b4a8b5a012
       
     1 // TODO unbind zoom behavior?
       
     2 d3.behavior.zoom = function() {
       
     3   var xyz = [0, 0, 0],
       
     4       event = d3.dispatch("zoom"),
       
     5       extent = d3_behavior_zoomInfiniteExtent;
       
     6 
       
     7   function zoom() {
       
     8     this
       
     9         .on("mousedown.zoom", mousedown)
       
    10         .on("mousewheel.zoom", mousewheel)
       
    11         .on("DOMMouseScroll.zoom", mousewheel)
       
    12         .on("dblclick.zoom", dblclick)
       
    13         .on("touchstart.zoom", touchstart);
       
    14 
       
    15     d3.select(window)
       
    16         .on("mousemove.zoom", d3_behavior_zoomMousemove)
       
    17         .on("mouseup.zoom", d3_behavior_zoomMouseup)
       
    18         .on("touchmove.zoom", d3_behavior_zoomTouchmove)
       
    19         .on("touchend.zoom", d3_behavior_zoomTouchup)
       
    20         .on("click.zoom", d3_behavior_zoomClick, true);
       
    21   }
       
    22 
       
    23   // snapshot the local context for subsequent dispatch
       
    24   function start() {
       
    25     d3_behavior_zoomXyz = xyz;
       
    26     d3_behavior_zoomExtent = extent;
       
    27     d3_behavior_zoomDispatch = event.zoom;
       
    28     d3_behavior_zoomEventTarget = d3.event.target;
       
    29     d3_behavior_zoomTarget = this;
       
    30     d3_behavior_zoomArguments = arguments;
       
    31   }
       
    32 
       
    33   function mousedown() {
       
    34     start.apply(this, arguments);
       
    35     d3_behavior_zoomPanning = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget));
       
    36     d3_behavior_zoomMoved = false;
       
    37     d3.event.preventDefault();
       
    38     window.focus();
       
    39   }
       
    40 
       
    41   // store starting mouse location
       
    42   function mousewheel() {
       
    43     start.apply(this, arguments);
       
    44     if (!d3_behavior_zoomZooming) d3_behavior_zoomZooming = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget));
       
    45     d3_behavior_zoomTo(d3_behavior_zoomDelta() + xyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomZooming);
       
    46   }
       
    47 
       
    48   function dblclick() {
       
    49     start.apply(this, arguments);
       
    50     var mouse = d3.svg.mouse(d3_behavior_zoomTarget);
       
    51     d3_behavior_zoomTo(d3.event.shiftKey ? Math.ceil(xyz[2] - 1) : Math.floor(xyz[2] + 1), mouse, d3_behavior_zoomLocation(mouse));
       
    52   }
       
    53 
       
    54   // doubletap detection
       
    55   function touchstart() {
       
    56     start.apply(this, arguments);
       
    57     var touches = d3_behavior_zoomTouchup(),
       
    58         touch,
       
    59         now = Date.now();
       
    60     if ((touches.length === 1) && (now - d3_behavior_zoomLast < 300)) {
       
    61       d3_behavior_zoomTo(1 + Math.floor(xyz[2]), touch = touches[0], d3_behavior_zoomLocations[touch.identifier]);
       
    62     }
       
    63     d3_behavior_zoomLast = now;
       
    64   }
       
    65 
       
    66   zoom.extent = function(x) {
       
    67     if (!arguments.length) return extent;
       
    68     extent = x == null ? d3_behavior_zoomInfiniteExtent : x;
       
    69     return zoom;
       
    70   };
       
    71 
       
    72   zoom.on = function(type, listener) {
       
    73     event.on(type, listener);
       
    74     return zoom;
       
    75   };
       
    76 
       
    77   return zoom;
       
    78 };
       
    79 
       
    80 var d3_behavior_zoomDiv,
       
    81     d3_behavior_zoomPanning,
       
    82     d3_behavior_zoomZooming,
       
    83     d3_behavior_zoomLocations = {}, // identifier -> location
       
    84     d3_behavior_zoomLast = 0,
       
    85     d3_behavior_zoomXyz,
       
    86     d3_behavior_zoomExtent,
       
    87     d3_behavior_zoomDispatch,
       
    88     d3_behavior_zoomEventTarget,
       
    89     d3_behavior_zoomTarget,
       
    90     d3_behavior_zoomArguments,
       
    91     d3_behavior_zoomMoved,
       
    92     d3_behavior_zoomStopClick;
       
    93 
       
    94 function d3_behavior_zoomLocation(point) {
       
    95   return [
       
    96     point[0] - d3_behavior_zoomXyz[0],
       
    97     point[1] - d3_behavior_zoomXyz[1],
       
    98     d3_behavior_zoomXyz[2]
       
    99   ];
       
   100 }
       
   101 
       
   102 // detect the pixels that would be scrolled by this wheel event
       
   103 function d3_behavior_zoomDelta() {
       
   104 
       
   105   // mousewheel events are totally broken!
       
   106   // https://bugs.webkit.org/show_bug.cgi?id=40441
       
   107   // not only that, but Chrome and Safari differ in re. to acceleration!
       
   108   if (!d3_behavior_zoomDiv) {
       
   109     d3_behavior_zoomDiv = d3.select("body").append("div")
       
   110         .style("visibility", "hidden")
       
   111         .style("top", 0)
       
   112         .style("height", 0)
       
   113         .style("width", 0)
       
   114         .style("overflow-y", "scroll")
       
   115       .append("div")
       
   116         .style("height", "2000px")
       
   117       .node().parentNode;
       
   118   }
       
   119 
       
   120   var e = d3.event, delta;
       
   121   try {
       
   122     d3_behavior_zoomDiv.scrollTop = 1000;
       
   123     d3_behavior_zoomDiv.dispatchEvent(e);
       
   124     delta = 1000 - d3_behavior_zoomDiv.scrollTop;
       
   125   } catch (error) {
       
   126     delta = e.wheelDelta || (-e.detail * 5);
       
   127   }
       
   128 
       
   129   return delta * .005;
       
   130 }
       
   131 
       
   132 // Note: Since we don't rotate, it's possible for the touches to become
       
   133 // slightly detached from their original positions. Thus, we recompute the
       
   134 // touch points on touchend as well as touchstart!
       
   135 function d3_behavior_zoomTouchup() {
       
   136   var touches = d3.svg.touches(d3_behavior_zoomTarget),
       
   137       i = -1,
       
   138       n = touches.length,
       
   139       touch;
       
   140   while (++i < n) d3_behavior_zoomLocations[(touch = touches[i]).identifier] = d3_behavior_zoomLocation(touch);
       
   141   return touches;
       
   142 }
       
   143 
       
   144 function d3_behavior_zoomTouchmove() {
       
   145   var touches = d3.svg.touches(d3_behavior_zoomTarget);
       
   146   switch (touches.length) {
       
   147 
       
   148     // single-touch pan
       
   149     case 1: {
       
   150       var touch = touches[0];
       
   151       d3_behavior_zoomTo(d3_behavior_zoomXyz[2], touch, d3_behavior_zoomLocations[touch.identifier]);
       
   152       break;
       
   153     }
       
   154 
       
   155     // double-touch pan + zoom
       
   156     case 2: {
       
   157       var p0 = touches[0],
       
   158           p1 = touches[1],
       
   159           p2 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2],
       
   160           l0 = d3_behavior_zoomLocations[p0.identifier],
       
   161           l1 = d3_behavior_zoomLocations[p1.identifier],
       
   162           l2 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2, l0[2]];
       
   163       d3_behavior_zoomTo(Math.log(d3.event.scale) / Math.LN2 + l0[2], p2, l2);
       
   164       break;
       
   165     }
       
   166   }
       
   167 }
       
   168 
       
   169 function d3_behavior_zoomMousemove() {
       
   170   d3_behavior_zoomZooming = null;
       
   171   if (d3_behavior_zoomPanning) {
       
   172     d3_behavior_zoomMoved = true;
       
   173     d3_behavior_zoomTo(d3_behavior_zoomXyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomPanning);
       
   174   }
       
   175 }
       
   176 
       
   177 function d3_behavior_zoomMouseup() {
       
   178   if (d3_behavior_zoomPanning) {
       
   179     if (d3_behavior_zoomMoved && d3_behavior_zoomEventTarget === d3.event.target) {
       
   180       d3_behavior_zoomStopClick = true;
       
   181     }
       
   182     d3_behavior_zoomMousemove();
       
   183     d3_behavior_zoomPanning = null;
       
   184   }
       
   185 }
       
   186 
       
   187 function d3_behavior_zoomClick() {
       
   188   if (d3_behavior_zoomStopClick && d3_behavior_zoomEventTarget === d3.event.target) {
       
   189     d3.event.stopPropagation();
       
   190     d3.event.preventDefault();
       
   191     d3_behavior_zoomStopClick = false;
       
   192     d3_behavior_zoomEventTarget = null;
       
   193   }
       
   194 }
       
   195 
       
   196 function d3_behavior_zoomTo(z, x0, x1) {
       
   197   z = d3_behavior_zoomExtentClamp(z, 2);
       
   198   var j = Math.pow(2, d3_behavior_zoomXyz[2]),
       
   199       k = Math.pow(2, z),
       
   200       K = Math.pow(2, (d3_behavior_zoomXyz[2] = z) - x1[2]),
       
   201       x_ = d3_behavior_zoomXyz[0],
       
   202       y_ = d3_behavior_zoomXyz[1],
       
   203       x = d3_behavior_zoomXyz[0] = d3_behavior_zoomExtentClamp((x0[0] - x1[0] * K), 0, k),
       
   204       y = d3_behavior_zoomXyz[1] = d3_behavior_zoomExtentClamp((x0[1] - x1[1] * K), 1, k),
       
   205       o = d3.event; // Events can be reentrant (e.g., focus).
       
   206 
       
   207   d3.event = {
       
   208     scale: k,
       
   209     translate: [x, y],
       
   210     transform: function(sx, sy) {
       
   211       if (sx) transform(sx, x_, x);
       
   212       if (sy) transform(sy, y_, y);
       
   213     }
       
   214   };
       
   215 
       
   216   function transform(scale, a, b) {
       
   217     scale.domain(scale.range().map(function(v) { return scale.invert(((v - b) * j) / k + a); }));
       
   218   }
       
   219 
       
   220   try {
       
   221     d3_behavior_zoomDispatch.apply(d3_behavior_zoomTarget, d3_behavior_zoomArguments);
       
   222   } finally {
       
   223     d3.event = o;
       
   224   }
       
   225 
       
   226   o.preventDefault();
       
   227 }
       
   228 
       
   229 var d3_behavior_zoomInfiniteExtent = [
       
   230   [-Infinity, Infinity],
       
   231   [-Infinity, Infinity],
       
   232   [-Infinity, Infinity]
       
   233 ];
       
   234 
       
   235 function d3_behavior_zoomExtentClamp(x, i, k) {
       
   236   var range = d3_behavior_zoomExtent[i],
       
   237       r0 = range[0],
       
   238       r1 = range[1];
       
   239   return arguments.length === 3
       
   240       ? Math.max(r1 * (r1 === Infinity ? -Infinity : 1 / k - 1),
       
   241         Math.min(r0 === -Infinity ? Infinity : r0, x / k)) * k
       
   242       : Math.max(r0, Math.min(r1, x));
       
   243 }