source: Dev/trunk/d3/src/behavior/zoom.js @ 76

Last change on this file since 76 was 76, checked in by fpvanagthoven, 14 years ago

d3

File size: 5.8 KB
Line 
1// TODO unbind zoom behavior?
2// TODO unbind listener?
3d3.behavior.zoom = function() {
4  var xyz = [0, 0, 0],
5      event = d3.dispatch("zoom");
6
7  function zoom() {
8    this
9        .on("mousedown.zoom", mousedown)
10        .on("mousewheel.zoom", mousewheel)
11        .on("DOMMouseScroll.zoom", dblclick)
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  }
21
22  // snapshot the local context for subsequent dispatch
23  function start() {
24    d3_behavior_zoomXyz = xyz;
25    d3_behavior_zoomDispatch = event.zoom.dispatch;
26    d3_behavior_zoomTarget = this;
27    d3_behavior_zoomArguments = arguments;
28  }
29
30  function mousedown() {
31    start.apply(this, arguments);
32    d3_behavior_zoomPanning = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget));
33    d3.event.preventDefault();
34    window.focus();
35  }
36
37  // store starting mouse location
38  function mousewheel() {
39    start.apply(this, arguments);
40    if (!d3_behavior_zoomZooming) d3_behavior_zoomZooming = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget));
41    d3_behavior_zoomTo(d3_behavior_zoomDelta() + xyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomZooming);
42  }
43
44  function dblclick() {
45    start.apply(this, arguments);
46    var mouse = d3.svg.mouse(d3_behavior_zoomTarget);
47    d3_behavior_zoomTo(d3.event.shiftKey ? Math.ceil(xyz[2] - 1) : Math.floor(xyz[2] + 1), mouse, d3_behavior_zoomLocation(mouse));
48  }
49
50  // doubletap detection
51  function touchstart() {
52    start.apply(this, arguments);
53    var touches = d3_behavior_zoomTouchup(),
54        touch,
55        now = Date.now();
56    if ((touches.length === 1) && (now - d3_behavior_zoomLast < 300)) {
57      d3_behavior_zoomTo(1 + Math.floor(xyz[2]), touch = touches[0], d3_behavior_zoomLocations[touch.identifier]);
58    }
59    d3_behavior_zoomLast = now;
60  }
61
62  zoom.on = function(type, listener) {
63    event[type].add(listener);
64    return zoom;
65  };
66
67  return zoom;
68};
69
70var d3_behavior_zoomDiv,
71    d3_behavior_zoomPanning,
72    d3_behavior_zoomZooming,
73    d3_behavior_zoomLocations = {}, // identifier -> location
74    d3_behavior_zoomLast = 0,
75    d3_behavior_zoomXyz,
76    d3_behavior_zoomDispatch,
77    d3_behavior_zoomTarget,
78    d3_behavior_zoomArguments;
79
80function d3_behavior_zoomLocation(point) {
81  return [
82    point[0] - d3_behavior_zoomXyz[0],
83    point[1] - d3_behavior_zoomXyz[1],
84    d3_behavior_zoomXyz[2]
85  ];
86}
87
88// detect the pixels that would be scrolled by this wheel event
89function d3_behavior_zoomDelta() {
90
91  // mousewheel events are totally broken!
92  // https://bugs.webkit.org/show_bug.cgi?id=40441
93  // not only that, but Chrome and Safari differ in re. to acceleration!
94  if (!d3_behavior_zoomDiv) {
95    d3_behavior_zoomDiv = d3.select("body").append("div")
96        .style("visibility", "hidden")
97        .style("top", 0)
98        .style("height", 0)
99        .style("width", 0)
100        .style("overflow-y", "scroll")
101      .append("div")
102        .style("height", "2000px")
103      .node().parentNode;
104  }
105
106  var e = d3.event, delta;
107  try {
108    d3_behavior_zoomDiv.scrollTop = 1000;
109    d3_behavior_zoomDiv.dispatchEvent(e);
110    delta = 1000 - d3_behavior_zoomDiv.scrollTop;
111  } catch (error) {
112    delta = e.wheelDelta || -e.detail;
113  }
114
115  return delta * .005;
116}
117
118// Note: Since we don't rotate, it's possible for the touches to become
119// slightly detached from their original positions. Thus, we recompute the
120// touch points on touchend as well as touchstart!
121function d3_behavior_zoomTouchup() {
122  var touches = d3.svg.touches(d3_behavior_zoomTarget),
123      i = -1,
124      n = touches.length,
125      touch;
126  while (++i < n) d3_behavior_zoomLocations[(touch = touches[i]).identifier] = d3_behavior_zoomLocation(touch);
127  return touches;
128}
129
130function d3_behavior_zoomTouchmove() {
131  var touches = d3.svg.touches(d3_behavior_zoomTarget);
132  switch (touches.length) {
133
134    // single-touch pan
135    case 1: {
136      var touch = touches[0];
137      d3_behavior_zoomTo(d3_behavior_zoomXyz[2], touch, d3_behavior_zoomLocations[touch.identifier]);
138      break;
139    }
140
141    // double-touch pan + zoom
142    case 2: {
143      var p0 = touches[0],
144          p1 = touches[1],
145          p2 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2],
146          l0 = d3_behavior_zoomLocations[p0.identifier],
147          l1 = d3_behavior_zoomLocations[p1.identifier],
148          l2 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2, l0[2]];
149      d3_behavior_zoomTo(Math.log(d3.event.scale) / Math.LN2 + l0[2], p2, l2);
150      break;
151    }
152  }
153}
154
155function d3_behavior_zoomMousemove() {
156  d3_behavior_zoomZooming = null;
157  if (d3_behavior_zoomPanning) d3_behavior_zoomTo(d3_behavior_zoomXyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomPanning);
158}
159
160function d3_behavior_zoomMouseup() {
161  if (d3_behavior_zoomPanning) {
162    d3_behavior_zoomMousemove();
163    d3_behavior_zoomPanning = null;
164  }
165}
166
167function d3_behavior_zoomTo(z, x0, x1) {
168  var K = Math.pow(2, (d3_behavior_zoomXyz[2] = z) - x1[2]),
169      x = d3_behavior_zoomXyz[0] = x0[0] - K * x1[0],
170      y = d3_behavior_zoomXyz[1] = x0[1] - K * x1[1],
171      o = d3.event, // Events can be reentrant (e.g., focus).
172      k = Math.pow(2, z);
173
174  d3.event = {
175    scale: k,
176    translate: [x, y],
177    transform: function(sx, sy) {
178      if (sx) transform(sx, x);
179      if (sy) transform(sy, y);
180    }
181  };
182
183  function transform(scale, o) {
184    var domain = scale.__domain || (scale.__domain = scale.domain()),
185        range = scale.range().map(function(v) { return (v - o) / k; });
186    scale.domain(domain).domain(range.map(scale.invert));
187  }
188
189  try {
190    d3_behavior_zoomDispatch.apply(d3_behavior_zoomTarget, d3_behavior_zoomArguments);
191  } finally {
192    d3.event = o;
193  }
194
195  o.preventDefault();
196}
Note: See TracBrowser for help on using the repository browser.