Comparing version 0.0.2 to 0.1.0
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-dispatch'), require('d3-interpolate'), require('d3-selection'), require('d3-transition')) : | ||
typeof define === 'function' && define.amd ? define(['exports', 'd3-dispatch', 'd3-interpolate', 'd3-selection', 'd3-transition'], factory) : | ||
(factory((global.d3_zoom = global.d3_zoom || {}),global.d3_dispatch,global.d3_interpolate,global.d3_selection,global.d3_transition)); | ||
}(this, function (exports,d3Dispatch,d3Interpolate,d3Selection,d3Transition) { 'use strict'; | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-dispatch'), require('d3-drag'), require('d3-interpolate'), require('d3-selection'), require('d3-transition')) : | ||
typeof define === 'function' && define.amd ? define(['exports', 'd3-dispatch', 'd3-drag', 'd3-interpolate', 'd3-selection', 'd3-transition'], factory) : | ||
(factory((global.d3_zoom = global.d3_zoom || {}),global.d3_dispatch,global.d3_drag,global.d3_interpolate,global.d3_selection,global.d3_transition)); | ||
}(this, function (exports,d3Dispatch,d3Drag,d3Interpolate,d3Selection,d3Transition) { 'use strict'; | ||
var version = "0.0.2"; | ||
var version = "0.1.0"; | ||
@@ -15,6 +15,6 @@ function constant(x) { | ||
function ZoomEvent(type, view) { | ||
function ZoomEvent(target, type, transform) { | ||
this.target = target; | ||
this.type = type; | ||
this.scale = view.k; | ||
this.translate = [view.x, view.y]; | ||
this.transform = transform; | ||
} | ||
@@ -30,14 +30,32 @@ | ||
constructor: Transform, | ||
scale: function(k) { | ||
return k === 1 ? this : new Transform(this.k * k, this.x, this.y); | ||
}, | ||
translate: function(x, y) { | ||
return x === 0 & y === 0 ? this : new Transform(this.k, this.x + this.k * x, this.y + this.k * y); | ||
}, | ||
apply: function(point) { | ||
return [point[0] * this.k + this.x, point[1] * this.k + this.y]; | ||
}, | ||
applyX: function(x) { | ||
return x * this.k + this.x; | ||
}, | ||
applyY: function(y) { | ||
return y * this.k + this.y; | ||
}, | ||
invert: function(location) { | ||
return [(location[0] - this.x) / this.k, (location[1] - this.y) / this.k]; | ||
}, | ||
scale: function(k) { | ||
return new Transform(this.k * k, this.x, this.y); | ||
invertX: function(x) { | ||
return (x - this.x) / this.k; | ||
}, | ||
translate: function(x, y) { | ||
return new Transform(this.k, this.x + this.k * x, this.y + this.k * y); | ||
invertY: function(y) { | ||
return (y - this.y) / this.k; | ||
}, | ||
rescaleX: function(x) { | ||
return x.copy().domain(x.range().map(this.invertX, this).map(x.invert, x)); | ||
}, | ||
rescaleY: function(y) { | ||
return y.copy().domain(y.range().map(this.invertY, this).map(y.invert, y)); | ||
}, | ||
toString: function() { | ||
@@ -53,9 +71,17 @@ return "translate(" + this.x + "," + this.y + ") scale(" + this.k + ")"; | ||
function transform(node) { | ||
return node == null ? identity : node.__zoom; | ||
return node && node.__zoom || identity; | ||
} | ||
// Ignore horizontal scrolling. | ||
function nopropagation() { | ||
d3Selection.event.stopImmediatePropagation(); | ||
} | ||
function noevent() { | ||
d3Selection.event.preventDefault(); | ||
d3Selection.event.stopImmediatePropagation(); | ||
} | ||
// Ignore right-click, since that should open the context menu. | ||
function defaultFilter() { | ||
return d3Selection.event.type === "wheel" ? d3Selection.event.deltaY : !d3Selection.event.button; | ||
return !d3Selection.event.button; | ||
} | ||
@@ -75,18 +101,14 @@ | ||
size = defaultSize, | ||
scaleMin = 0, | ||
scaleMax = Infinity, | ||
k0 = 0, | ||
k1 = Infinity, | ||
duration = 250, | ||
zooming = 0, | ||
gestures = [], | ||
listeners = d3Dispatch.dispatch("start", "zoom", "end").on("start", started), | ||
mousemoving, | ||
touchstarting, | ||
touchending, | ||
touchDelay = 500, | ||
wheelTimer, | ||
wheelDelay = 150, | ||
centerPoint = null, | ||
centerLocation, | ||
mousePoint, | ||
mouseLocation; | ||
wheelDelay = 150; | ||
// TODO Prevent default. | ||
// TODO Stop propagation. | ||
var listeners = d3Dispatch.dispatch("start", "zoom", "end") | ||
.on("start", started); | ||
function zoom(selection) { | ||
@@ -108,4 +130,12 @@ selection | ||
selection.property("__zoom", defaultTransform); | ||
if (collection instanceof d3Transition.transition) schedule(collection, transform, centerPoint); | ||
else collection.interrupt().each(emitStart).property("__zoom", transform).each(emitZoom).each(emitEnd); | ||
if (collection !== selection) { | ||
schedule(collection, transform); | ||
} else { | ||
selection.interrupt().each(function() { | ||
gesture(this, arguments) | ||
.start() | ||
.zoom(null, typeof transform === "function" ? transform.apply(this, arguments) : transform) | ||
.end(); | ||
}); | ||
} | ||
}; | ||
@@ -123,14 +153,24 @@ | ||
zoom.transform(selection, function() { | ||
var p0 = centerPoint || (p0 = size.apply(this, arguments), [p0[0] / 2, p0[1] / 2]), | ||
p1 = this.__zoom.invert(p0), | ||
var t0 = this.__zoom, | ||
p0 = (p0 = size.apply(this, arguments), [p0[0] / 2, p0[1] / 2]), | ||
p1 = t0.invert(p0), | ||
k1 = typeof k === "function" ? k.apply(this, arguments) : k; | ||
return translate(scale(this.__zoom, k1), p0, p1); | ||
return translate(scale(t0, k1), p0, p1); | ||
}); | ||
}; | ||
zoom.translateBy = function(selection, x, y) { | ||
zoom.transform(selection, function() { | ||
return this.__zoom.translate( | ||
typeof x === "function" ? x.apply(this, arguments) : x, | ||
typeof y === "function" ? y.apply(this, arguments) : y | ||
); | ||
}); | ||
}; | ||
function clamp(transform) { | ||
return function() { | ||
var t = typeof transform === "function" ? transform.apply(this, arguments) : transform; | ||
if (scaleMin > t.k || t.k > scaleMax) { | ||
var p0 = centerPoint || (p0 = size.apply(this, arguments), [p0[0] / 2, p0[1] / 2]), | ||
if (k0 > t.k || t.k > k1) { | ||
var p0 = (p0 = size.apply(this, arguments), [p0[0] / 2, p0[1] / 2]), | ||
p1 = t.invert(p0); | ||
@@ -144,3 +184,3 @@ t = translate(scale(t, t.k), p0, p1); | ||
function scale(transform, k) { | ||
return new Transform(Math.max(scaleMin, Math.min(scaleMax, k)), transform.x, transform.y); | ||
return new Transform(Math.max(k0, Math.min(k1, k)), transform.x, transform.y); | ||
} | ||
@@ -154,7 +194,8 @@ | ||
transition | ||
.on("start.zoom", emitStart) | ||
.on("interrupt.zoom end.zoom", emitEnd) | ||
.on("start.zoom", function() { gesture(this, arguments).start(); }) | ||
.on("interrupt.zoom end.zoom", function() { gesture(this, arguments).end(); }) | ||
.tween("zoom:zoom", function() { | ||
var that = this, | ||
args = arguments, | ||
g = gesture(that, args), | ||
s = size.apply(that, args), | ||
@@ -167,5 +208,5 @@ p = center || [s[0] / 2, s[1] / 2], | ||
return function(t) { | ||
if (t === 1) that.__zoom = b; // Avoid rounding error on end. | ||
else { var l = i(t), k = w / l[2]; that.__zoom = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k); } | ||
emitZoom.apply(that, args); | ||
if (t === 1) t = b; // Avoid rounding error on end. | ||
else { var l = i(t), k = w / l[2]; t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k); } | ||
g.zoom(null, t); | ||
}; | ||
@@ -175,85 +216,110 @@ }); | ||
function emitStart() { | ||
if (++zooming === 1) emit("start", this, arguments); | ||
function gesture(that, args) { | ||
for (var i = 0, n = gestures.length, g; i < n; ++i) { | ||
if ((g = gestures[i]).that === that) { | ||
return g; | ||
} | ||
} | ||
return new Gesture(that, args); | ||
} | ||
function emitZoom() { | ||
emit("zoom", this, arguments); | ||
function Gesture(that, args) { | ||
this.that = that; | ||
this.args = args; | ||
this.index = -1; | ||
this.active = 0; | ||
} | ||
function emitEnd() { | ||
if (--zooming === 0) emit("end", this, arguments); | ||
} | ||
Gesture.prototype = { | ||
start: function() { | ||
if (++this.active === 1) { | ||
this.index = gestures.push(this) - 1; | ||
this.emit("start"); | ||
} | ||
return this; | ||
}, | ||
zoom: function(key, transform) { | ||
if (this.wheel && key !== "wheel") this.wheel[1] = transform.invert(this.wheel[0]); | ||
if (this.mouse && key !== "mouse") this.mouse[1] = transform.invert(this.mouse[0]); | ||
if (this.touch0 && key !== "touch") this.touch0[1] = transform.invert(this.touch0[0]); | ||
if (this.touch1 && key !== "touch") this.touch1[1] = transform.invert(this.touch1[0]); | ||
this.that.__zoom = transform; | ||
this.emit("zoom"); | ||
return this; | ||
}, | ||
end: function() { | ||
if (--this.active === 0) { | ||
gestures.splice(this.index, 1); | ||
this.index = -1; | ||
this.emit("end"); | ||
} | ||
return this; | ||
}, | ||
emit: function(type) { | ||
d3Selection.customEvent(new ZoomEvent(zoom, type, this.that.__zoom), listeners.apply, listeners, [type, this.that, this.args]); | ||
} | ||
}; | ||
function emit(type, that, args) { | ||
d3Selection.customEvent(new ZoomEvent(type, that.__zoom), listeners.apply, listeners, [type, that, args]); | ||
} | ||
// TODO Clean this up. | ||
function wheeled() { | ||
if (!filter.apply(this, arguments)) return; | ||
var g = gesture(this, arguments), | ||
p0, | ||
p1, | ||
y = -d3Selection.event.deltaY * (d3Selection.event.deltaMode ? 120 : 1) / 500, | ||
t = this.__zoom, | ||
k = t.k; | ||
var that = this, | ||
args = arguments, | ||
transform = that.__zoom; | ||
// If this wheel event won’t trigger a transform change, ignore it. | ||
if (y === 0 || (y < 0 && k === k0) || (y > 0 && k === k1)) return; | ||
if (wheelTimer) clearTimeout(wheelTimer); | ||
// If there were recently wheel events, use the existing point and location. | ||
if (g.wheel) { | ||
p0 = g.wheel[0], p1 = g.wheel[1]; | ||
clearTimeout(wheelTimer); | ||
} | ||
// If this is the first wheel event since the wheel was idle, then capture | ||
// the mouse location and the center location to avoid loss of precision | ||
// over the duration of the gesture: if you zoom in a lot and then zoom out, | ||
// we want you to return to the original location exactly. | ||
// Otherwise, capture the mouse point and location at the start. | ||
else { | ||
if (centerPoint) centerLocation = transform.invert(centerPoint); | ||
mouseLocation = transform.invert(mousePoint = d3Selection.mouse(that)); | ||
d3Transition.interrupt(that), emitStart.apply(that, args); | ||
g.wheel = [p0 = d3Selection.mouse(this), p1 = t.invert(p0)]; | ||
d3Transition.interrupt(this); | ||
g.start(); | ||
} | ||
transform = scale(transform, transform.k * Math.pow(2, -d3Selection.event.deltaY * (d3Selection.event.deltaMode ? 120 : 1) / 500)); | ||
// There may be a concurrent mousedown-mouseup gesture! Scaling around an | ||
// explicit center changes the mouse location, so must update the mouse | ||
// location that was captured on mousedown. | ||
if (centerPoint) { | ||
transform = translate(transform, centerPoint, centerLocation); | ||
mouseLocation = transform.invert(mousePoint); | ||
} else { | ||
transform = translate(transform, mousePoint, mouseLocation); | ||
} | ||
that.__zoom = transform; | ||
d3Selection.event.preventDefault(); | ||
noevent(); | ||
wheelTimer = setTimeout(wheelidled, wheelDelay); | ||
emitZoom.apply(that, args); | ||
g.zoom("wheel", translate(scale(t, k * Math.pow(2, y)), p0, p1)); | ||
function wheelidled() { | ||
wheelTimer = null; | ||
emitEnd.apply(that, args); | ||
delete g.wheel; | ||
g.end(); | ||
} | ||
} | ||
// TODO Clean this up. | ||
function mousedowned() { | ||
if (!filter.apply(this, arguments)) return; | ||
if (touchending || !filter.apply(this, arguments)) return; | ||
var g = gesture(this, arguments), | ||
v = d3Selection.select(d3Selection.event.view).on("mousemove.zoom", mousemoved, true).on("mouseup.zoom", mouseupped, true), | ||
p0 = d3Selection.mouse(this), | ||
p1 = this.__zoom.invert(p0); | ||
var that = this, | ||
args = arguments; | ||
d3Drag.dragDisable(d3Selection.event.view); | ||
nopropagation(); | ||
mousemoving = false; | ||
g.mouse = [p0, p1]; | ||
d3Transition.interrupt(this); | ||
g.start(); | ||
// We shouldn’t capture that.__zoom on mousedown because you can wheel after | ||
// mousedown and before mouseup. If that happens AND an explicit center is | ||
// defined, the center location also needs to be updated. | ||
mouseLocation = that.__zoom.invert(mousePoint = d3Selection.mouse(that)); | ||
d3Selection.select(d3Selection.event.view).on("mousemove.zoom", mousemoved, true).on("mouseup.zoom", mouseupped, true); | ||
d3Transition.interrupt(that), emitStart.apply(that, args); | ||
function mousemoved() { | ||
that.__zoom = translate(that.__zoom, mousePoint = d3Selection.mouse(that), mouseLocation); | ||
if (centerPoint) centerLocation = that.__zoom.invert(centerPoint); | ||
emitZoom.apply(that, args); | ||
noevent(); | ||
mousemoving = true; | ||
g.zoom("mouse", translate(g.that.__zoom, g.mouse[0] = d3Selection.mouse(g.that), g.mouse[1])); | ||
} | ||
function mouseupped() { | ||
d3Selection.select(d3Selection.event.view).on("mousemove.zoom mouseup.zoom", null); | ||
emitEnd.apply(that, args); | ||
v.on("mousemove.zoom mouseup.zoom", null); | ||
d3Drag.dragEnable(d3Selection.event.view, mousemoving); | ||
noevent(); | ||
delete g.mouse; | ||
g.end(); | ||
} | ||
@@ -265,20 +331,78 @@ } | ||
var t0 = this.__zoom, | ||
p0 = centerPoint || d3Selection.mouse(this), | ||
p0 = d3Selection.mouse(this), | ||
p1 = t0.invert(p0), | ||
k1 = t0.k * (d3Selection.event.shiftKey ? 0.5 : 2), | ||
t1 = translate(scale(t0, k1), p0, p1); | ||
noevent(); | ||
if (duration > 0) d3Selection.select(this).transition().duration(duration).call(schedule, t1, p0); | ||
else this.__zoom = t1; | ||
else d3Selection.select(this).call(zoom.transform, t1); | ||
} | ||
function touchstarted() { | ||
// TODO | ||
if (!filter.apply(this, arguments)) return; | ||
var g = gesture(this, arguments), | ||
touches = d3Selection.event.changedTouches, | ||
n = touches.length, i, t, p; | ||
nopropagation(); | ||
for (i = 0; i < n; ++i) { | ||
t = touches[i], p = d3Selection.touch(this, touches, t.identifier); | ||
p = [p, this.__zoom.invert(p), t.identifier]; | ||
if (!g.touch0) g.touch0 = p; | ||
else if (!g.touch1) g.touch1 = p; | ||
} | ||
if (touchstarting) { | ||
touchstarting = clearTimeout(touchstarting); | ||
if (!g.touch1) return g.end(), dblclicked.apply(this, arguments); | ||
} | ||
if (d3Selection.event.touches.length === n) { | ||
touchstarting = setTimeout(function() { touchstarting = null; }, touchDelay); | ||
d3Transition.interrupt(this); | ||
g.start(); | ||
} | ||
} | ||
function touchmoved() { | ||
// TODO | ||
var g = gesture(this, arguments), | ||
touches = d3Selection.event.changedTouches, | ||
n = touches.length, i, t, p, l; | ||
noevent(); | ||
if (touchstarting) touchstarting = clearTimeout(touchstarting); | ||
for (i = 0; i < n; ++i) { | ||
t = touches[i], p = d3Selection.touch(this, touches, t.identifier); | ||
if (g.touch0 && g.touch0[2] === t.identifier) g.touch0[0] = p; | ||
else if (g.touch1 && g.touch1[2] === t.identifier) g.touch1[0] = p; | ||
} | ||
t = g.that.__zoom; | ||
if (g.touch1) { | ||
var p0 = g.touch0[0], l0 = g.touch0[1], | ||
p1 = g.touch1[0], l1 = g.touch1[1], | ||
dp = (dp = p1[0] - p0[0]) * dp + (dp = p1[1] - p0[1]) * dp, | ||
dl = (dl = l1[0] - l0[0]) * dl + (dl = l1[1] - l0[1]) * dl; | ||
t = scale(t, Math.sqrt(dp / dl)); | ||
p = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2]; | ||
l = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2]; | ||
} | ||
else if (g.touch0) p = g.touch0[0], l = g.touch0[1]; | ||
else return; | ||
g.zoom("touch", translate(t, p, l)); | ||
} | ||
function touchended() { | ||
// TODO | ||
var g = gesture(this, arguments), | ||
touches = d3Selection.event.changedTouches, | ||
n = touches.length, i, t; | ||
nopropagation(); | ||
if (touchending) clearTimeout(touchending); | ||
touchending = setTimeout(function() { touchending = null; }, touchDelay); | ||
for (i = 0; i < n; ++i) { | ||
t = touches[i]; | ||
if (g.touch0 && g.touch0[2] === t.identifier) delete g.touch0; | ||
else if (g.touch1 && g.touch1[2] === t.identifier) delete g.touch1; | ||
} | ||
if (g.touch1 && !g.touch0) g.touch0 = g.touch1, delete g.touch1; | ||
if (!g.touch0) g.end(); | ||
} | ||
@@ -295,9 +419,5 @@ | ||
zoom.scaleExtent = function(_) { | ||
return arguments.length ? (scaleMin = +_[0], scaleMax = +_[1], zoom) : [scaleMin, scaleMax]; | ||
return arguments.length ? (k0 = +_[0], k1 = +_[1], zoom) : [k0, k1]; | ||
}; | ||
zoom.center = function(_) { | ||
return arguments.length ? (centerPoint = _ == null ? null : [+_[0], +_[1]], zoom) : centerPoint; | ||
}; | ||
zoom.duration = function(_) { | ||
@@ -304,0 +424,0 @@ return arguments.length ? (duration = +_, zoom) : duration; |
@@ -1,1 +0,1 @@ | ||
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("d3-dispatch"),require("d3-interpolate"),require("d3-selection"),require("d3-transition")):"function"==typeof define&&define.amd?define(["exports","d3-dispatch","d3-interpolate","d3-selection","d3-transition"],n):n(t.d3_zoom=t.d3_zoom||{},t.d3_dispatch,t.d3_interpolate,t.d3_selection,t.d3_transition)}(this,function(t,n,o,e,i){"use strict";function r(t){return function(){return t}}function u(t,n){this.type=t,this.scale=n.k,this.translate=[n.x,n.y]}function s(t,n,o){this.k=t,this.x=n,this.y=o}function c(t){return null==t?m:t.__zoom}function a(){return"wheel"===e.event.type?e.event.deltaY:!e.event.button}function p(){var t=this.ownerSVGElement||this;return[t.clientWidth,t.clientHeight]}function l(){return this.__zoom||m}function f(t){function c(t){t.on("wheel.zoom",k).on("mousedown.zoom",w).on("dblclick.zoom",x).on("touchstart.zoom",g).on("touchmove.zoom",b).on("touchend.zoom touchcancel.zoom",M).style("-webkit-tap-highlight-color","rgba(0,0,0,0)").property("__zoom",l)}function f(t){return function(){var n="function"==typeof t?t.apply(this,arguments):t;if(B>n.k||n.k>D){var o=V||(o=j.apply(this,arguments),[o[0]/2,o[1]/2]),e=n.invert(o);n=m(h(n,n.k),o,e)}return n}}function h(t,n){return new s(Math.max(B,Math.min(D,n)),t.x,t.y)}function m(t,n,o){return o=t.apply(o),new s(t.k,t.x+n[0]-o[0],t.y+n[1]-o[1])}function y(t,n,e){t.on("start.zoom",_).on("interrupt.zoom end.zoom",z).tween("zoom:zoom",function(){var t=this,i=arguments,r=j.apply(t,i),u=e||[r[0]/2,r[1]/2],c=Math.max(r[0],r[1]),a=t.__zoom,p="function"==typeof n?n.apply(t,i):n,l=o.interpolateZoom(a.invert(u).concat(c/a.k),p.invert(u).concat(c/p.k));return function(n){if(1===n)t.__zoom=p;else{var o=l(n),e=c/o[2];t.__zoom=new s(e,u[0]-o[0]*e,u[1]-o[1]*e)}v.apply(t,i)}})}function _(){1===++H&&d("start",this,arguments)}function v(){d("zoom",this,arguments)}function z(){0===--H&&d("end",this,arguments)}function d(t,n,o){e.customEvent(new u(t,n.__zoom),W.apply,W,[t,n,o])}function k(){function t(){T=null,z.apply(n,o)}if(Y.apply(this,arguments)){var n=this,o=arguments,r=n.__zoom;T?clearTimeout(T):(V&&(q=r.invert(V)),S=r.invert(E=e.mouse(n)),i.interrupt(n),_.apply(n,o)),r=h(r,r.k*Math.pow(2,-e.event.deltaY*(e.event.deltaMode?120:1)/500)),V?(r=m(r,V,q),S=r.invert(E)):r=m(r,E,S),n.__zoom=r,e.event.preventDefault(),T=setTimeout(t,K),v.apply(n,o)}}function w(){function t(){o.__zoom=m(o.__zoom,E=e.mouse(o),S),V&&(q=o.__zoom.invert(V)),v.apply(o,r)}function n(){e.select(e.event.view).on("mousemove.zoom mouseup.zoom",null),z.apply(o,r)}if(Y.apply(this,arguments)){var o=this,r=arguments;S=o.__zoom.invert(E=e.mouse(o)),e.select(e.event.view).on("mousemove.zoom",t,!0).on("mouseup.zoom",n,!0),i.interrupt(o),_.apply(o,r)}}function x(){if(Y.apply(this,arguments)){var t=this.__zoom,n=V||e.mouse(this),o=t.invert(n),i=t.k*(e.event.shiftKey?.5:2),r=m(h(t,i),n,o);G>0?e.select(this).transition().duration(G).call(y,r,n):this.__zoom=r}}function g(){}function b(){}function M(){}var T,q,E,S,Y=a,j=p,B=0,D=1/0,G=250,H=0,K=150,V=null,W=n.dispatch("start","zoom","end").on("start",t);return c.transform=function(t,n){var o=t.selection?t.selection():t;n=f(n),o.property("__zoom",l),t instanceof i.transition?y(t,n,V):t.interrupt().each(_).property("__zoom",n).each(v).each(z)},c.scaleBy=function(t,n){c.scaleTo(t,function(){var t=this.__zoom.k,o="function"==typeof n?n.apply(this,arguments):n;return t*o})},c.scaleTo=function(t,n){c.transform(t,function(){var t=V||(t=j.apply(this,arguments),[t[0]/2,t[1]/2]),o=this.__zoom.invert(t),e="function"==typeof n?n.apply(this,arguments):n;return m(h(this.__zoom,e),t,o)})},c.filter=function(t){return arguments.length?(Y="function"==typeof t?t:r(!!t),c):Y},c.size=function(t){return arguments.length?(j="function"==typeof t?t:r([+t[0],+t[1]]),c):j},c.scaleExtent=function(t){return arguments.length?(B=+t[0],D=+t[1],c):[B,D]},c.center=function(t){return arguments.length?(V=null==t?null:[+t[0],+t[1]],c):V},c.duration=function(t){return arguments.length?(G=+t,c):G},c.on=function(){var t=W.on.apply(W,arguments);return t===W?c:t},c}var h="0.0.2";s.prototype={constructor:s,apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},scale:function(t){return new s(this.k*t,this.x,this.y)},translate:function(t,n){return new s(this.k,this.x+this.k*t,this.y+this.k*n)},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var m=new s(1,0,0);c.prototype=s.prototype,t.version=h,t.zoom=f,t.zoomTransform=c}); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("d3-dispatch"),require("d3-drag"),require("d3-interpolate"),require("d3-selection"),require("d3-transition")):"function"==typeof define&&define.amd?define(["exports","d3-dispatch","d3-drag","d3-interpolate","d3-selection","d3-transition"],e):e(t.d3_zoom=t.d3_zoom||{},t.d3_dispatch,t.d3_drag,t.d3_interpolate,t.d3_selection,t.d3_transition)}(this,function(t,e,n,o,i,r){"use strict";function u(t){return function(){return t}}function h(t,e,n){this.target=t,this.type=e,this.transform=n}function s(t,e,n){this.k=t,this.x=e,this.y=n}function c(t){return t&&t.__zoom||y}function a(){i.event.stopImmediatePropagation()}function f(){i.event.preventDefault(),i.event.stopImmediatePropagation()}function l(){return!i.event.button}function m(){var t=this.ownerSVGElement||this;return[t.clientWidth,t.clientHeight]}function p(){return this.__zoom||y}function d(t){function c(t){t.on("wheel.zoom",w).on("mousedown.zoom",k).on("dblclick.zoom",x).on("touchstart.zoom",T).on("touchmove.zoom",b).on("touchend.zoom touchcancel.zoom",q).style("-webkit-tap-highlight-color","rgba(0,0,0,0)").property("__zoom",p)}function d(t){return function(){var e="function"==typeof t?t.apply(this,arguments):t;if(I>e.k||e.k>P){var n=(n=D.apply(this,arguments),[n[0]/2,n[1]/2]),o=e.invert(n);e=y(v(e,e.k),n,o)}return e}}function v(t,e){return new s(Math.max(I,Math.min(P,e)),t.x,t.y)}function y(t,e,n){return n=t.apply(n),new s(t.k,t.x+e[0]-n[0],t.y+e[1]-n[1])}function z(t,e,n){t.on("start.zoom",function(){_(this,arguments).start()}).on("interrupt.zoom end.zoom",function(){_(this,arguments).end()}).tween("zoom:zoom",function(){var t=this,i=arguments,r=_(t,i),u=D.apply(t,i),h=n||[u[0]/2,u[1]/2],c=Math.max(u[0],u[1]),a=t.__zoom,f="function"==typeof e?e.apply(t,i):e,l=o.interpolateZoom(a.invert(h).concat(c/a.k),f.invert(h).concat(c/f.k));return function(t){if(1===t)t=f;else{var e=l(t),n=c/e[2];t=new s(n,h[0]-e[0]*n,h[1]-e[1]*n)}r.zoom(null,t)}})}function _(t,e){for(var n,o=0,i=j.length;i>o;++o)if((n=j[o]).that===t)return n;return new g(t,e)}function g(t,e){this.that=t,this.args=e,this.index=-1,this.active=0}function w(){function t(){X=null,delete o.wheel,o.end()}if(B.apply(this,arguments)){var e,n,o=_(this,arguments),u=-i.event.deltaY*(i.event.deltaMode?120:1)/500,h=this.__zoom,s=h.k;0===u||0>u&&s===I||u>0&&s===P||(o.wheel?(e=o.wheel[0],n=o.wheel[1],clearTimeout(X)):(o.wheel=[e=i.mouse(this),n=h.invert(e)],r.interrupt(this),o.start()),f(),X=setTimeout(t,K),o.zoom("wheel",y(v(h,s*Math.pow(2,u)),e,n)))}}function k(){function t(){f(),M=!0,o.zoom("mouse",y(o.that.__zoom,o.mouse[0]=i.mouse(o.that),o.mouse[1]))}function e(){u.on("mousemove.zoom mouseup.zoom",null),n.dragEnable(i.event.view,M),f(),delete o.mouse,o.end()}if(!E&&B.apply(this,arguments)){var o=_(this,arguments),u=i.select(i.event.view).on("mousemove.zoom",t,!0).on("mouseup.zoom",e,!0),h=i.mouse(this),s=this.__zoom.invert(h);n.dragDisable(i.event.view),a(),M=!1,o.mouse=[h,s],r.interrupt(this),o.start()}}function x(){if(B.apply(this,arguments)){var t=this.__zoom,e=i.mouse(this),n=t.invert(e),o=t.k*(i.event.shiftKey?.5:2),r=y(v(t,o),e,n);f(),S>0?i.select(this).transition().duration(S).call(z,r,e):i.select(this).call(c.transform,r)}}function T(){if(B.apply(this,arguments)){var t,e,n,o=_(this,arguments),u=i.event.changedTouches,h=u.length;for(a(),t=0;h>t;++t)e=u[t],n=i.touch(this,u,e.identifier),n=[n,this.__zoom.invert(n),e.identifier],o.touch0?o.touch1||(o.touch1=n):o.touch0=n;return Y&&(Y=clearTimeout(Y),!o.touch1)?(o.end(),x.apply(this,arguments)):void(i.event.touches.length===h&&(Y=setTimeout(function(){Y=null},H),r.interrupt(this),o.start()))}}function b(){var t,e,n,o,r=_(this,arguments),u=i.event.changedTouches,h=u.length;for(f(),Y&&(Y=clearTimeout(Y)),t=0;h>t;++t)e=u[t],n=i.touch(this,u,e.identifier),r.touch0&&r.touch0[2]===e.identifier?r.touch0[0]=n:r.touch1&&r.touch1[2]===e.identifier&&(r.touch1[0]=n);if(e=r.that.__zoom,r.touch1){var s=r.touch0[0],c=r.touch0[1],a=r.touch1[0],l=r.touch1[1],m=(m=a[0]-s[0])*m+(m=a[1]-s[1])*m,p=(p=l[0]-c[0])*p+(p=l[1]-c[1])*p;e=v(e,Math.sqrt(m/p)),n=[(s[0]+a[0])/2,(s[1]+a[1])/2],o=[(c[0]+l[0])/2,(c[1]+l[1])/2]}else{if(!r.touch0)return;n=r.touch0[0],o=r.touch0[1]}r.zoom("touch",y(e,n,o))}function q(){var t,e,n=_(this,arguments),o=i.event.changedTouches,r=o.length;for(a(),E&&clearTimeout(E),E=setTimeout(function(){E=null},H),t=0;r>t;++t)e=o[t],n.touch0&&n.touch0[2]===e.identifier?delete n.touch0:n.touch1&&n.touch1[2]===e.identifier&&delete n.touch1;n.touch1&&!n.touch0&&(n.touch0=n.touch1,delete n.touch1),n.touch0||n.end()}var M,Y,E,X,B=l,D=m,I=0,P=1/0,S=250,j=[],G=e.dispatch("start","zoom","end").on("start",t),H=500,K=150;return c.transform=function(t,e){var n=t.selection?t.selection():t;e=d(e),n.property("__zoom",p),t!==n?z(t,e):n.interrupt().each(function(){_(this,arguments).start().zoom(null,"function"==typeof e?e.apply(this,arguments):e).end()})},c.scaleBy=function(t,e){c.scaleTo(t,function(){var t=this.__zoom.k,n="function"==typeof e?e.apply(this,arguments):e;return t*n})},c.scaleTo=function(t,e){c.transform(t,function(){var t=this.__zoom,n=(n=D.apply(this,arguments),[n[0]/2,n[1]/2]),o=t.invert(n),i="function"==typeof e?e.apply(this,arguments):e;return y(v(t,i),n,o)})},c.translateBy=function(t,e,n){c.transform(t,function(){return this.__zoom.translate("function"==typeof e?e.apply(this,arguments):e,"function"==typeof n?n.apply(this,arguments):n)})},g.prototype={start:function(){return 1===++this.active&&(this.index=j.push(this)-1,this.emit("start")),this},zoom:function(t,e){return this.wheel&&"wheel"!==t&&(this.wheel[1]=e.invert(this.wheel[0])),this.mouse&&"mouse"!==t&&(this.mouse[1]=e.invert(this.mouse[0])),this.touch0&&"touch"!==t&&(this.touch0[1]=e.invert(this.touch0[0])),this.touch1&&"touch"!==t&&(this.touch1[1]=e.invert(this.touch1[0])),this.that.__zoom=e,this.emit("zoom"),this},end:function(){return 0===--this.active&&(j.splice(this.index,1),this.index=-1,this.emit("end")),this},emit:function(t){i.customEvent(new h(c,t,this.that.__zoom),G.apply,G,[t,this.that,this.args])}},c.filter=function(t){return arguments.length?(B="function"==typeof t?t:u(!!t),c):B},c.size=function(t){return arguments.length?(D="function"==typeof t?t:u([+t[0],+t[1]]),c):D},c.scaleExtent=function(t){return arguments.length?(I=+t[0],P=+t[1],c):[I,P]},c.duration=function(t){return arguments.length?(S=+t,c):S},c.on=function(){var t=G.on.apply(G,arguments);return t===G?c:t},c}var v="0.1.0";s.prototype={constructor:s,scale:function(t){return 1===t?this:new s(this.k*t,this.x,this.y)},translate:function(t,e){return 0===t&0===e?this:new s(this.k,this.x+this.k*t,this.y+this.k*e)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var y=new s(1,0,0);c.prototype=s.prototype,t.version=v,t.zoom=d,t.zoomTransform=c}); |
export var name = "d3-zoom"; | ||
export var version = "0.0.2"; | ||
export var version = "0.1.0"; | ||
export var description = ""; | ||
@@ -10,4 +10,4 @@ export var keywords = ["d3","zoom","behavior","interaction"]; | ||
export var repository = {"type":"git","url":"https://github.com/d3/d3-zoom.git"}; | ||
export var scripts = {"pretest":"rm -rf build && mkdir build && json2module package.json > build/package.js && rollup -g d3-dispatch:d3_dispatch,d3-interpolate:d3_interpolate,d3-selection:d3_selection,d3-transition:d3_transition -f umd -n d3_zoom -o build/d3-zoom.js -- index.js","test":"tape 'test/**/*-test.js' && eslint index.js src","prepublish":"npm run test && uglifyjs build/d3-zoom.js -c -m -o build/d3-zoom.min.js","postpublish":"VERSION=`node -e 'console.log(require(\"./package.json\").version)'`; git push && git push --tags && cp build/d3-zoom.js ../d3.github.com/d3-zoom.v0.0.js && cp build/d3-zoom.min.js ../d3.github.com/d3-zoom.v0.0.min.js && cd ../d3.github.com && git add d3-zoom.v0.0.js d3-zoom.v0.0.min.js && git commit -m \"d3-zoom ${VERSION}\" && git push && cd - && zip -j build/d3-zoom.zip -- LICENSE README.md build/d3-zoom.js build/d3-zoom.min.js"}; | ||
export var dependencies = {"d3-dispatch":"~0.4.3","d3-interpolate":"~0.7.0","d3-selection":"~0.7.2","d3-transition":"~0.2.8"}; | ||
export var scripts = {"pretest":"rm -rf build && mkdir build && json2module package.json > build/package.js && rollup -g d3-dispatch:d3_dispatch,d3-drag:d3_drag,d3-interpolate:d3_interpolate,d3-selection:d3_selection,d3-transition:d3_transition -f umd -n d3_zoom -o build/d3-zoom.js -- index.js","test":"tape 'test/**/*-test.js' && eslint index.js src","prepublish":"npm run test && uglifyjs build/d3-zoom.js -c -m -o build/d3-zoom.min.js","postpublish":"VERSION=`node -e 'console.log(require(\"./package.json\").version)'`; git push && git push --tags && cp build/d3-zoom.js ../d3.github.com/d3-zoom.v0.1.js && cp build/d3-zoom.min.js ../d3.github.com/d3-zoom.v0.1.min.js && cd ../d3.github.com && git add d3-zoom.v0.1.js d3-zoom.v0.1.min.js && git commit -m \"d3-zoom ${VERSION}\" && git push && cd - && zip -j build/d3-zoom.zip -- LICENSE README.md build/d3-zoom.js build/d3-zoom.min.js"}; | ||
export var dependencies = {"d3-dispatch":"~0.4.3","d3-drag":"~0.2.0","d3-interpolate":"~0.8.0","d3-selection":"~0.7.2","d3-transition":"~0.2.8"}; | ||
export var devDependencies = {"eslint":"2","json2module":"0.0","rollup":"0.26","tape":"4","uglify-js":"2"}; |
{ | ||
"name": "d3-zoom", | ||
"version": "0.0.2", | ||
"version": "0.1.0", | ||
"description": "", | ||
@@ -24,10 +24,11 @@ "keywords": [ | ||
"scripts": { | ||
"pretest": "rm -rf build && mkdir build && json2module package.json > build/package.js && rollup -g d3-dispatch:d3_dispatch,d3-interpolate:d3_interpolate,d3-selection:d3_selection,d3-transition:d3_transition -f umd -n d3_zoom -o build/d3-zoom.js -- index.js", | ||
"pretest": "rm -rf build && mkdir build && json2module package.json > build/package.js && rollup -g d3-dispatch:d3_dispatch,d3-drag:d3_drag,d3-interpolate:d3_interpolate,d3-selection:d3_selection,d3-transition:d3_transition -f umd -n d3_zoom -o build/d3-zoom.js -- index.js", | ||
"test": "tape 'test/**/*-test.js' && eslint index.js src", | ||
"prepublish": "npm run test && uglifyjs build/d3-zoom.js -c -m -o build/d3-zoom.min.js", | ||
"postpublish": "VERSION=`node -e 'console.log(require(\"./package.json\").version)'`; git push && git push --tags && cp build/d3-zoom.js ../d3.github.com/d3-zoom.v0.0.js && cp build/d3-zoom.min.js ../d3.github.com/d3-zoom.v0.0.min.js && cd ../d3.github.com && git add d3-zoom.v0.0.js d3-zoom.v0.0.min.js && git commit -m \"d3-zoom ${VERSION}\" && git push && cd - && zip -j build/d3-zoom.zip -- LICENSE README.md build/d3-zoom.js build/d3-zoom.min.js" | ||
"postpublish": "VERSION=`node -e 'console.log(require(\"./package.json\").version)'`; git push && git push --tags && cp build/d3-zoom.js ../d3.github.com/d3-zoom.v0.1.js && cp build/d3-zoom.min.js ../d3.github.com/d3-zoom.v0.1.min.js && cd ../d3.github.com && git add d3-zoom.v0.1.js d3-zoom.v0.1.min.js && git commit -m \"d3-zoom ${VERSION}\" && git push && cd - && zip -j build/d3-zoom.zip -- LICENSE README.md build/d3-zoom.js build/d3-zoom.min.js" | ||
}, | ||
"dependencies": { | ||
"d3-dispatch": "~0.4.3", | ||
"d3-interpolate": "~0.7.0", | ||
"d3-drag": "~0.2.0", | ||
"d3-interpolate": "~0.8.0", | ||
"d3-selection": "~0.7.2", | ||
@@ -34,0 +35,0 @@ "d3-transition": "~0.2.8" |
241
README.md
@@ -7,3 +7,3 @@ # d3-zoom | ||
If you use NPM, `npm install d3-zoom`. Otherwise, download the [latest release](https://github.com/d3/d3-zoom/releases/latest). You can also load directly from [d3js.org](https://d3js.org), either as a [standalone library](https://d3js.org/d3-zoom.v0.0.min.js) or as part of [D3 4.0](https://github.com/d3/d3). AMD, CommonJS, and vanilla environments are supported. In vanilla, a `d3_zoom` global is exported: | ||
If you use NPM, `npm install d3-zoom`. Otherwise, download the [latest release](https://github.com/d3/d3-zoom/releases/latest). You can also load directly from [d3js.org](https://d3js.org), either as a [standalone library](https://d3js.org/d3-zoom.v0.1.min.js) or as part of [D3 4.0](https://github.com/d3/d3). AMD, CommonJS, and vanilla environments are supported. In vanilla, a `d3_zoom` global is exported: | ||
@@ -14,7 +14,8 @@ ```html | ||
<script src="https://d3js.org/d3-ease.v0.7.min.js"></script> | ||
<script src="https://d3js.org/d3-interpolate.v0.7.min.js"></script> | ||
<script src="https://d3js.org/d3-interpolate.v0.8.min.js"></script> | ||
<script src="https://d3js.org/d3-selection.v0.7.min.js"></script> | ||
<script src="https://d3js.org/d3-timer.v0.4.min.js"></script> | ||
<script src="https://d3js.org/d3-transition.v0.2.min.js"></script> | ||
<script src="https://d3js.org/d3-zoom.v0.0.min.js"></script> | ||
<script src="https://d3js.org/d3-drag.v0.2.min.js"></script> | ||
<script src="https://d3js.org/d3-zoom.v0.1.min.js"></script> | ||
<script> | ||
@@ -31,4 +32,238 @@ | ||
This table describes how the zoom behavior interprets native events: | ||
| Event | Listening Element | Zoom Event | Default Prevented? | | ||
| ------------ | ----------------- | ----------- | ------------------ | | ||
| mousedown⁵ | selection | start | no¹ | | ||
| mousemove² | window¹ | zoom | yes | | ||
| mouseup² | window¹ | end | yes | | ||
| dragstart² | window | - | yes | | ||
| selectstart² | window | - | yes | | ||
| click³ | window | - | yes | | ||
| dblclick | selection | *multiple*⁶ | yes | | ||
| wheel | selection | zoom⁷ | yes | | ||
| touchstart | selection | *multiple*⁶ | no⁴ | | ||
| touchmove | selection | zoom | yes | | ||
| touchend | selection | end | no⁴ | | ||
| touchcancel | selection | end | no⁴ | | ||
The propagation of all consumed events is [immediately stopped](https://dom.spec.whatwg.org/#dom-event-stopimmediatepropagation). | ||
¹ Necessary to capture events outside an iframe; see [d3-drag#9](https://github.com/d3/d3-drag/issues/9). | ||
<br>² Only applies during an active, mouse-based gesture; see [d3-drag#9](https://github.com/d3/d3-drag/issues/9). | ||
<br>³ Only applies immediately after a non-empty, mouse-based gesture. | ||
<br>⁴ Necessary to allow [click emulation](https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html#//apple_ref/doc/uid/TP40006511-SW7) on touch input; see [d3-drag#9](https://github.com/d3/d3-drag/issues/9). | ||
<br>⁵ Ignored if within 500ms of a touch gesture ending; assumes [click emulation](https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html#//apple_ref/doc/uid/TP40006511-SW7). | ||
<br>⁶ Double-click and double-tap initiate a transition that emits start, zoom and end events. | ||
<br>⁷ The first wheel event emits a start event; an end event is emitted when no wheel events are received for 150ms. | ||
<a href="#zoom" name="zoom">#</a> d3.<b>zoom</b>() | ||
Creates a new zoom behavior. The returned behavior, [*zoom*](#_drag), is both an object and a function, and is typically applied to selected elements via [*selection*.call](https://github.com/d3/d3-selection#selection_call). | ||
<a href="#_zoom" name="_zoom">#</a> <i>zoom</i>(<i>selection</i>) | ||
Applies this zoom behavior to the specified [*selection*](https://github.com/d3/d3-selection). This function is typically not invoked directly, and is instead invoked via [*selection*.call](https://github.com/d3/d3-selection#selection_call). For example, to instantiate a zoom behavior and apply it to a selection: | ||
```js | ||
d3.selectAll(".node").call(d3.zoom().on("zoom", zoomed)); | ||
``` | ||
Internally, the zoom behavior uses [*selection*.on](https://github.com/d3/d3-selection#selection_on) to bind the necessary event listeners for zooming. The listeners use the name `.zoom`, so you can subsequently unbind the zoom behavior as follows: | ||
```js | ||
selection.on(".zoom", null); | ||
``` | ||
Applying the zoom behavior also sets the [-webkit-tap-highlight-color](https://developer.apple.com/library/mac/documentation/AppleApplications/Reference/SafariWebContent/AdjustingtheTextSize/AdjustingtheTextSize.html#//apple_ref/doc/uid/TP40006510-SW5) style to transparent, disabling the tap highlight on iOS. If you want a different tap highlight color, remove or re-apply this style after applying the drag behavior. | ||
<a href="#zoom_transform" name="zoom_transform">#</a> <i>zoom</i>.<b>transform</b>(<i>selection</i>, <i>transform</i>) | ||
… | ||
<a href="#zoom_translateBy" name="zoom_translateBy">#</a> <i>zoom</i>.<b>translateBy</b>(<i>selection</i>, <i>x</i>, <i>y</i>) | ||
… A convenience alias for [*zoom*.transform](#zoom_transform). | ||
<a href="#zoom_scaleBy" name="zoom_scaleBy">#</a> <i>zoom</i>.<b>scaleBy</b>(<i>selection</i>, <i>k</i>) | ||
… A convenience alias for [*zoom*.transform](#zoom_transform). | ||
<a href="#zoom_scaleTo" name="zoom_scaleTo">#</a> <i>zoom</i>.<b>scaleTo</b>(<i>selection</i>, <i>k</i>) | ||
… A convenience alias for [*zoom*.transform](#zoom_transform). | ||
<a href="#zoom_filter" name="zoom_filter">#</a> <i>zoom</i>.<b>filter</b>([<i>filter</i>]) | ||
If *filter* is specified, sets the filter to the specified function and returns the zoom behavior. If *filter* is not specified, returns the current filter, which defaults to: | ||
```js | ||
function filter() { | ||
return !event.button; | ||
} | ||
``` | ||
If the filter returns falsey, the initiating event is ignored and no zoom gestures are started. Thus, the filter determines which input events are ignored. The default filter ignores mousedown events on secondary buttons, since those buttons are typically intended for other purposes, such as the context menu. | ||
<a href="#zoom_size" name="zoom_size">#</a> <i>zoom</i>.<b>size</b>([<i>size</i>]) | ||
… | ||
```js | ||
function size() { | ||
var node = this.ownerSVGElement || this; | ||
return [node.clientWidth, node.clientHeight]; | ||
} | ||
``` | ||
<a href="#zoom_scaleExtent" name="zoom_scaleExtent">#</a> <i>zoom</i>.<b>scaleExtent</b>([<i>scaleExtent</i>]) | ||
… Defaults to [0, ∞]. | ||
<a href="#zoom_duration" name="zoom_duration">#</a> <i>zoom</i>.<b>duration</b>([<i>duration</i>]) | ||
If *duration* is specified, sets the duration for zoom transitions on double-click and double-tap to the specified number of milliseconds and returns the zoom behavior. If *duration* is not specified, returns the current duration, which defaults to 250 milliseconds. If the duration is not greater than zero, double-click and -tap trigger instantaneous changes to the zoom transform rather than initiating smooth transitions. | ||
<a href="#zoom_on" name="zoom_on">#</a> <i>zoom</i>.<b>on</b>(<i>typenames</i>[, <i>listener</i>]) | ||
If *listener* is specified, sets the event *listener* for the specified *typenames* and returns the zoom behavior. If an event listener was already registered for the same type and name, the existing listener is removed before the new listener is added. If *listener* is null, removes the current event listeners for the specified *typenames*, if any. If *listener* is not specified, returns the first currently-assigned listener matching the specified *typenames*, if any. When a specified event is dispatched, each *listener* will be invoked with the same context and arguments as [*selection*.on](https://github.com/d3/d3-selection#selection_on) listeners: the current datum `d` and index `i`, with the `this` context as the current DOM element. | ||
The *typenames* is a string containing one or more *typename* separated by whitespace. Each *typename* is a *type*, optionally followed by a period (`.`) and a *name*, such as `zoom.foo` and `zoom.bar`; the name allows multiple listeners to be registered for the same *type*. The *type* must be one of the following: | ||
* `start` - after zooming begins (such as on mousedown). | ||
* `zoom` - after a change to the zoom transform (such as on mousemove). | ||
* `end` - after zooming ends (such as on mouseup ). | ||
See [*dispatch*.on](https://github.com/d3/d3-dispatch#dispatch_on) for more. | ||
### Zoom Events | ||
When a [zoom event listener](#zoom_on) is invoked, [d3.event](https://github.com/d3/d3-selection#event) is set to the current zoom event. The *event* object exposes several fields: | ||
* `target` - the associated [zoom behavior](#zoom). | ||
* `type` - the string “start”, “zoom” or “end”; see [*zoom*.on](#zoom_on). | ||
* `transform` - the current [zoom transform](#zoom-transforms). | ||
* `sourceEvent` - the underlying input event, such as mousemove or touchmove. | ||
### Zoom Transforms | ||
The zoom behavior stores the zoom state on the element to which the zoom behavior was [applied](#_zoom), not on the zoom behavior itself. This is because the zoom behavior can be applied to many elements simultaneously, and each element can be zoomed independently. The zoom state can change either on user interaction or programmatically via [*zoom*.transform](#zoom_transform). | ||
To retrieve the zoom state, use *event*.transform on the current [zoom event](#zoom-events) within a zoom event listener (see [*zoom*.on](#zoom_on)), or use [d3.zoomTransform](#zoomTransform) for a given node. The latter is particularly useful for modifying the zoom state programmatically, say to implement buttons for zooming in and out. | ||
<a href="#zoomTransform" name="zoomTransform">#</a> d3.<b>zoomTransform</b>([<i>node</i>]) | ||
Returns the current transform for the specified *node*. Internally, an element’s transform is stored as *element*.\_\_zoom; however, you should use this method rather than accessing it directly. If a *node* is not specified, or the given *node* has no defined transform, returns the identity transformation where *k* = 1, *t<sub>x</sub>* = *t<sub>y</sub>* = 0. The returned transform represents a two-dimensional [transformation matrix](https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations) of the form: | ||
*k* 0 *t<sub>x</sub>* | ||
<br>0 *k* *t<sub>y</sub>* | ||
<br>0 0 1 | ||
(This matrix is capable of representing only scale and translation; a future release may also allow rotation, though this would probably not be a backwards-compatible change.) The position ⟨*x*,*y*⟩ is transformed to ⟨*x* × *k* + *t<sub>x</sub>*,*y* × *k* + *t<sub>y</sub>*⟩. The transform object exposes the following properties: | ||
* `x` - the translation amount *t<sub>x</sub>* along the *x*-axis. | ||
* `y` - the translation amount *t<sub>y</sub>* along the *y*-axis. | ||
* `k` - the scale factor *k*. | ||
These properties should be considered read-only; instead of mutating a transform, use [*transform*.scale](#transform_scale) and [*transform*.translate](#transform_translate) to derive a new transform. Also see [*zoom*.scaleBy](#zoom_scaleBy), [*zoom*.scaleTo](#zoom_scaleTo) and [*zoom*.translateBy](#zoom_translateBy) for convenience methods on the zoom behavior. To create a transform with a given *k*, *t<sub>x</sub>*, and *t<sub>y</sub>*: | ||
```js | ||
var t = d3.zoomTransform().translate(x, y).scale(k); | ||
``` | ||
To apply the transformation to a [Canvas 2D context](https://www.w3.org/TR/2dcontext/), use [*context*.translate](https://www.w3.org/TR/2dcontext/#dom-context-2d-translate) followed by [*context*.scale](https://www.w3.org/TR/2dcontext/#dom-context-2d-scale): | ||
```js | ||
context.translate(transform.x, transform.y); | ||
context.scale(transform.k, transform.k); | ||
``` | ||
Similarly, to apply the transformation to HTML elements via [CSS](https://www.w3.org/TR/css-transforms-1/): | ||
```js | ||
div.style("transform", "translate(" + transform.x + "px," + transform.y + "px) scale(" + transform.k + ")"); | ||
``` | ||
To apply the transformation to [SVG](https://www.w3.org/TR/SVG/coords.html#TransformAttribute): | ||
```js | ||
g.attr("transform", "translate(" + transform.x + "," + transform.y + ") scale(" + transform.k + ")"); | ||
``` | ||
Or more simply, taking advantage of [*transform*.toString](#transform_toString): | ||
```js | ||
g.attr("transform", transform); | ||
``` | ||
Note that the order of transformations matters! The translate must be applied before the scale. | ||
<a href="#transform_scale" name="transform_scale">#</a> <i>transform</i>.<b>scale</b>(<i>k</i>) | ||
Returns a transform whose scale *k₁* is equal to *k₀* × *k*, where *k₀* is this transform’s scale. | ||
<a href="#transform_translate" name="transform_translate">#</a> <i>transform</i>.<b>translate</b>(<i>x</i>, <i>y</i>) | ||
Returns a transform whose translation *t<sub>x1</sub>* and *t<sub>y1</sub>* is equal to *t<sub>x0</sub>* + *x* and *t<sub>y0</sub>* + *y*, where *t<sub>x0</sub>* and *t<sub>y0</sub>* is this transform’s translation. | ||
<a href="#transform_apply" name="transform_apply">#</a> <i>transform</i>.<b>apply</b>(<i>point</i>) | ||
Returns the transformation of the specified *point* which is a two-element array of numbers [*x*, *y*]. The returned point is equal to [*x* × *k* + *t<sub>x</sub>*, *y* × *k* + *t<sub>y</sub>*]. | ||
<a href="#transform_applyX" name="transform_applyX">#</a> <i>transform</i>.<b>applyX</b>(<i>x</i>) | ||
Returns the transformation of the specified *x*-coordinate, *x* × *k* + *t<sub>x</sub>*. | ||
<a href="#transform_applyy" name="transform_applyy">#</a> <i>transform</i>.<b>applyY</b>(<i>y</i>) | ||
Returns the transformation of the specified *y*-coordinate, *y* × *k* + *t<sub>y</sub>*. | ||
<a href="#transform_invert" name="transform_invert">#</a> <i>transform</i>.<b>invert</b>(<i>point</i>) | ||
Returns the inverse transformation of the specified *point* which is a two-element array of numbers [*x*, *y*]. The returned point is equal to [(*x* - *t<sub>x</sub>*) / *k*, (*y* - *t<sub>y</sub>*) / *k*]. | ||
<a href="#transform_invertX" name="transform_invertX">#</a> <i>transform</i>.<b>invertX</b>(<i>x</i>) | ||
Returns the inverse transformation of the specified *x*-coordinate, (*x* - *t<sub>x</sub>*) / *k*. | ||
<a href="#transform_inverty" name="transform_inverty">#</a> <i>transform</i>.<b>invertY</b>(<i>y</i>) | ||
Returns the inverse transformation of the specified *y*-coordinate, (*y* - *t<sub>y</sub>*) / *k*. | ||
<a href="#transform_rescaleX" name="transform_rescaleX">#</a> <i>transform</i>.<b>rescaleX</b>(<i>x</i>) | ||
Returns a [copy](https://github.com/d3/d3-scale#continuous_copy) of the [continuous scale](https://github.com/d3/d3-scale#continuous-scales) *x* whose [domain](https://github.com/d3/d3-scale#continuous_domain) is transformed. This is implemented by first applying the [inverse *x*-transform](#transform_invertX) on the scale’s [range](https://github.com/d3/d3-scale#continuous_range), and then applying the [inverse scale](https://github.com/d3/d3-scale#continuous_invert) to compute the corresponding domain: | ||
```js | ||
function rescaleX(x) { | ||
var range = x.range().map(transform.invertX, transform), | ||
domain = range.map(x.invert, x); | ||
return x.copy().domain(domain); | ||
} | ||
``` | ||
This method does not modify the input scale *x*; *x* thus represents the untransformed scale, while the returned scale represents its transformed view. | ||
<a href="#transform_rescaley" name="transform_rescaley">#</a> <i>transform</i>.<b>rescaleY</b>(<i>y</i>) | ||
Returns a [copy](https://github.com/d3/d3-scale#continuous_copy) of the [continuous scale](https://github.com/d3/d3-scale#continuous-scales) *y* whose [domain](https://github.com/d3/d3-scale#continuous_domain) is transformed. This is implemented by first applying the [inverse *y*-transform](#transform_invertY) on the scale’s [range](https://github.com/d3/d3-scale#continuous_range), and then applying the [inverse scale](https://github.com/d3/d3-scale#continuous_invert) to compute the corresponding domain: | ||
```js | ||
function rescaleY(y) { | ||
var range = y.range().map(transform.invertY, transform), | ||
domain = range.map(y.invert, y); | ||
return y.copy().domain(domain); | ||
} | ||
``` | ||
This method does not modify the input scale *y*; *y* thus represents the untransformed scale, while the returned scale represents its transformed view. | ||
<a href="#transform_toString" name="transform_toString">#</a> <i>transform</i>.<b>toString</b>() | ||
Returns a string representing the [SVG transform](https://www.w3.org/TR/SVG/coords.html#TransformAttribute) corresponding to this transform. Implemented as: | ||
```js | ||
function toString() { | ||
return "translate(" + this.x + "," + this.y + ") scale(" + this.k + ")"; | ||
} | ||
``` |
@@ -1,5 +0,5 @@ | ||
export default function ZoomEvent(type, view) { | ||
export default function ZoomEvent(target, type, transform) { | ||
this.target = target; | ||
this.type = type; | ||
this.scale = view.k; | ||
this.translate = [view.x, view.y]; | ||
this.transform = transform; | ||
} |
@@ -9,14 +9,32 @@ export function Transform(k, x, y) { | ||
constructor: Transform, | ||
scale: function(k) { | ||
return k === 1 ? this : new Transform(this.k * k, this.x, this.y); | ||
}, | ||
translate: function(x, y) { | ||
return x === 0 & y === 0 ? this : new Transform(this.k, this.x + this.k * x, this.y + this.k * y); | ||
}, | ||
apply: function(point) { | ||
return [point[0] * this.k + this.x, point[1] * this.k + this.y]; | ||
}, | ||
applyX: function(x) { | ||
return x * this.k + this.x; | ||
}, | ||
applyY: function(y) { | ||
return y * this.k + this.y; | ||
}, | ||
invert: function(location) { | ||
return [(location[0] - this.x) / this.k, (location[1] - this.y) / this.k]; | ||
}, | ||
scale: function(k) { | ||
return new Transform(this.k * k, this.x, this.y); | ||
invertX: function(x) { | ||
return (x - this.x) / this.k; | ||
}, | ||
translate: function(x, y) { | ||
return new Transform(this.k, this.x + this.k * x, this.y + this.k * y); | ||
invertY: function(y) { | ||
return (y - this.y) / this.k; | ||
}, | ||
rescaleX: function(x) { | ||
return x.copy().domain(x.range().map(this.invertX, this).map(x.invert, x)); | ||
}, | ||
rescaleY: function(y) { | ||
return y.copy().domain(y.range().map(this.invertY, this).map(y.invert, y)); | ||
}, | ||
toString: function() { | ||
@@ -32,3 +50,3 @@ return "translate(" + this.x + "," + this.y + ") scale(" + this.k + ")"; | ||
export default function transform(node) { | ||
return node == null ? identity : node.__zoom; | ||
return node && node.__zoom || identity; | ||
} |
287
src/zoom.js
import {dispatch} from "d3-dispatch"; | ||
import {dragDisable, dragEnable} from "d3-drag"; | ||
import {interpolateZoom} from "d3-interpolate"; | ||
import {event, customEvent, select, mouse} from "d3-selection"; | ||
import {interrupt, transition} from "d3-transition"; | ||
import {event, customEvent, select, mouse, touch} from "d3-selection"; | ||
import {interrupt} from "d3-transition"; | ||
import constant from "./constant"; | ||
import ZoomEvent from "./event"; | ||
import {Transform, identity} from "./transform"; | ||
import noevent, {nopropagation} from "./noevent"; | ||
// Ignore horizontal scrolling. | ||
// Ignore right-click, since that should open the context menu. | ||
function defaultFilter() { | ||
return event.type === "wheel" ? event.deltaY : !event.button; | ||
return !event.button; | ||
} | ||
@@ -27,18 +28,14 @@ | ||
size = defaultSize, | ||
scaleMin = 0, | ||
scaleMax = Infinity, | ||
k0 = 0, | ||
k1 = Infinity, | ||
duration = 250, | ||
zooming = 0, | ||
gestures = [], | ||
listeners = dispatch("start", "zoom", "end").on("start", started), | ||
mousemoving, | ||
touchstarting, | ||
touchending, | ||
touchDelay = 500, | ||
wheelTimer, | ||
wheelDelay = 150, | ||
centerPoint = null, | ||
centerLocation, | ||
mousePoint, | ||
mouseLocation; | ||
wheelDelay = 150; | ||
// TODO Prevent default. | ||
// TODO Stop propagation. | ||
var listeners = dispatch("start", "zoom", "end") | ||
.on("start", started); | ||
function zoom(selection) { | ||
@@ -60,4 +57,12 @@ selection | ||
selection.property("__zoom", defaultTransform); | ||
if (collection instanceof transition) schedule(collection, transform, centerPoint); | ||
else collection.interrupt().each(emitStart).property("__zoom", transform).each(emitZoom).each(emitEnd); | ||
if (collection !== selection) { | ||
schedule(collection, transform); | ||
} else { | ||
selection.interrupt().each(function() { | ||
gesture(this, arguments) | ||
.start() | ||
.zoom(null, typeof transform === "function" ? transform.apply(this, arguments) : transform) | ||
.end(); | ||
}); | ||
} | ||
}; | ||
@@ -75,14 +80,24 @@ | ||
zoom.transform(selection, function() { | ||
var p0 = centerPoint || (p0 = size.apply(this, arguments), [p0[0] / 2, p0[1] / 2]), | ||
p1 = this.__zoom.invert(p0), | ||
var t0 = this.__zoom, | ||
p0 = (p0 = size.apply(this, arguments), [p0[0] / 2, p0[1] / 2]), | ||
p1 = t0.invert(p0), | ||
k1 = typeof k === "function" ? k.apply(this, arguments) : k; | ||
return translate(scale(this.__zoom, k1), p0, p1); | ||
return translate(scale(t0, k1), p0, p1); | ||
}); | ||
}; | ||
zoom.translateBy = function(selection, x, y) { | ||
zoom.transform(selection, function() { | ||
return this.__zoom.translate( | ||
typeof x === "function" ? x.apply(this, arguments) : x, | ||
typeof y === "function" ? y.apply(this, arguments) : y | ||
); | ||
}); | ||
}; | ||
function clamp(transform) { | ||
return function() { | ||
var t = typeof transform === "function" ? transform.apply(this, arguments) : transform; | ||
if (scaleMin > t.k || t.k > scaleMax) { | ||
var p0 = centerPoint || (p0 = size.apply(this, arguments), [p0[0] / 2, p0[1] / 2]), | ||
if (k0 > t.k || t.k > k1) { | ||
var p0 = (p0 = size.apply(this, arguments), [p0[0] / 2, p0[1] / 2]), | ||
p1 = t.invert(p0); | ||
@@ -96,3 +111,3 @@ t = translate(scale(t, t.k), p0, p1); | ||
function scale(transform, k) { | ||
return new Transform(Math.max(scaleMin, Math.min(scaleMax, k)), transform.x, transform.y); | ||
return new Transform(Math.max(k0, Math.min(k1, k)), transform.x, transform.y); | ||
} | ||
@@ -106,7 +121,8 @@ | ||
transition | ||
.on("start.zoom", emitStart) | ||
.on("interrupt.zoom end.zoom", emitEnd) | ||
.on("start.zoom", function() { gesture(this, arguments).start(); }) | ||
.on("interrupt.zoom end.zoom", function() { gesture(this, arguments).end(); }) | ||
.tween("zoom:zoom", function() { | ||
var that = this, | ||
args = arguments, | ||
g = gesture(that, args), | ||
s = size.apply(that, args), | ||
@@ -119,5 +135,5 @@ p = center || [s[0] / 2, s[1] / 2], | ||
return function(t) { | ||
if (t === 1) that.__zoom = b; // Avoid rounding error on end. | ||
else { var l = i(t), k = w / l[2]; that.__zoom = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k); } | ||
emitZoom.apply(that, args); | ||
if (t === 1) t = b; // Avoid rounding error on end. | ||
else { var l = i(t), k = w / l[2]; t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k); } | ||
g.zoom(null, t); | ||
}; | ||
@@ -127,85 +143,110 @@ }); | ||
function emitStart() { | ||
if (++zooming === 1) emit("start", this, arguments); | ||
function gesture(that, args) { | ||
for (var i = 0, n = gestures.length, g; i < n; ++i) { | ||
if ((g = gestures[i]).that === that) { | ||
return g; | ||
} | ||
} | ||
return new Gesture(that, args); | ||
} | ||
function emitZoom() { | ||
emit("zoom", this, arguments); | ||
function Gesture(that, args) { | ||
this.that = that; | ||
this.args = args; | ||
this.index = -1; | ||
this.active = 0; | ||
} | ||
function emitEnd() { | ||
if (--zooming === 0) emit("end", this, arguments); | ||
} | ||
Gesture.prototype = { | ||
start: function() { | ||
if (++this.active === 1) { | ||
this.index = gestures.push(this) - 1; | ||
this.emit("start"); | ||
} | ||
return this; | ||
}, | ||
zoom: function(key, transform) { | ||
if (this.wheel && key !== "wheel") this.wheel[1] = transform.invert(this.wheel[0]); | ||
if (this.mouse && key !== "mouse") this.mouse[1] = transform.invert(this.mouse[0]); | ||
if (this.touch0 && key !== "touch") this.touch0[1] = transform.invert(this.touch0[0]); | ||
if (this.touch1 && key !== "touch") this.touch1[1] = transform.invert(this.touch1[0]); | ||
this.that.__zoom = transform; | ||
this.emit("zoom"); | ||
return this; | ||
}, | ||
end: function() { | ||
if (--this.active === 0) { | ||
gestures.splice(this.index, 1); | ||
this.index = -1; | ||
this.emit("end"); | ||
} | ||
return this; | ||
}, | ||
emit: function(type) { | ||
customEvent(new ZoomEvent(zoom, type, this.that.__zoom), listeners.apply, listeners, [type, this.that, this.args]); | ||
} | ||
}; | ||
function emit(type, that, args) { | ||
customEvent(new ZoomEvent(type, that.__zoom), listeners.apply, listeners, [type, that, args]); | ||
} | ||
// TODO Clean this up. | ||
function wheeled() { | ||
if (!filter.apply(this, arguments)) return; | ||
var g = gesture(this, arguments), | ||
p0, | ||
p1, | ||
y = -event.deltaY * (event.deltaMode ? 120 : 1) / 500, | ||
t = this.__zoom, | ||
k = t.k; | ||
var that = this, | ||
args = arguments, | ||
transform = that.__zoom; | ||
// If this wheel event won’t trigger a transform change, ignore it. | ||
if (y === 0 || (y < 0 && k === k0) || (y > 0 && k === k1)) return; | ||
if (wheelTimer) clearTimeout(wheelTimer); | ||
// If there were recently wheel events, use the existing point and location. | ||
if (g.wheel) { | ||
p0 = g.wheel[0], p1 = g.wheel[1]; | ||
clearTimeout(wheelTimer); | ||
} | ||
// If this is the first wheel event since the wheel was idle, then capture | ||
// the mouse location and the center location to avoid loss of precision | ||
// over the duration of the gesture: if you zoom in a lot and then zoom out, | ||
// we want you to return to the original location exactly. | ||
// Otherwise, capture the mouse point and location at the start. | ||
else { | ||
if (centerPoint) centerLocation = transform.invert(centerPoint); | ||
mouseLocation = transform.invert(mousePoint = mouse(that)); | ||
interrupt(that), emitStart.apply(that, args); | ||
g.wheel = [p0 = mouse(this), p1 = t.invert(p0)]; | ||
interrupt(this); | ||
g.start(); | ||
} | ||
transform = scale(transform, transform.k * Math.pow(2, -event.deltaY * (event.deltaMode ? 120 : 1) / 500)); | ||
// There may be a concurrent mousedown-mouseup gesture! Scaling around an | ||
// explicit center changes the mouse location, so must update the mouse | ||
// location that was captured on mousedown. | ||
if (centerPoint) { | ||
transform = translate(transform, centerPoint, centerLocation); | ||
mouseLocation = transform.invert(mousePoint); | ||
} else { | ||
transform = translate(transform, mousePoint, mouseLocation); | ||
} | ||
that.__zoom = transform; | ||
event.preventDefault(); | ||
noevent(); | ||
wheelTimer = setTimeout(wheelidled, wheelDelay); | ||
emitZoom.apply(that, args); | ||
g.zoom("wheel", translate(scale(t, k * Math.pow(2, y)), p0, p1)); | ||
function wheelidled() { | ||
wheelTimer = null; | ||
emitEnd.apply(that, args); | ||
delete g.wheel; | ||
g.end(); | ||
} | ||
} | ||
// TODO Clean this up. | ||
function mousedowned() { | ||
if (!filter.apply(this, arguments)) return; | ||
if (touchending || !filter.apply(this, arguments)) return; | ||
var g = gesture(this, arguments), | ||
v = select(event.view).on("mousemove.zoom", mousemoved, true).on("mouseup.zoom", mouseupped, true), | ||
p0 = mouse(this), | ||
p1 = this.__zoom.invert(p0); | ||
var that = this, | ||
args = arguments; | ||
dragDisable(event.view); | ||
nopropagation(); | ||
mousemoving = false; | ||
g.mouse = [p0, p1]; | ||
interrupt(this); | ||
g.start(); | ||
// We shouldn’t capture that.__zoom on mousedown because you can wheel after | ||
// mousedown and before mouseup. If that happens AND an explicit center is | ||
// defined, the center location also needs to be updated. | ||
mouseLocation = that.__zoom.invert(mousePoint = mouse(that)); | ||
select(event.view).on("mousemove.zoom", mousemoved, true).on("mouseup.zoom", mouseupped, true); | ||
interrupt(that), emitStart.apply(that, args); | ||
function mousemoved() { | ||
that.__zoom = translate(that.__zoom, mousePoint = mouse(that), mouseLocation); | ||
if (centerPoint) centerLocation = that.__zoom.invert(centerPoint); | ||
emitZoom.apply(that, args); | ||
noevent(); | ||
mousemoving = true; | ||
g.zoom("mouse", translate(g.that.__zoom, g.mouse[0] = mouse(g.that), g.mouse[1])); | ||
} | ||
function mouseupped() { | ||
select(event.view).on("mousemove.zoom mouseup.zoom", null); | ||
emitEnd.apply(that, args); | ||
v.on("mousemove.zoom mouseup.zoom", null); | ||
dragEnable(event.view, mousemoving); | ||
noevent(); | ||
delete g.mouse; | ||
g.end(); | ||
} | ||
@@ -217,20 +258,78 @@ } | ||
var t0 = this.__zoom, | ||
p0 = centerPoint || mouse(this), | ||
p0 = mouse(this), | ||
p1 = t0.invert(p0), | ||
k1 = t0.k * (event.shiftKey ? 0.5 : 2), | ||
t1 = translate(scale(t0, k1), p0, p1); | ||
noevent(); | ||
if (duration > 0) select(this).transition().duration(duration).call(schedule, t1, p0); | ||
else this.__zoom = t1; | ||
else select(this).call(zoom.transform, t1); | ||
} | ||
function touchstarted() { | ||
// TODO | ||
if (!filter.apply(this, arguments)) return; | ||
var g = gesture(this, arguments), | ||
touches = event.changedTouches, | ||
n = touches.length, i, t, p; | ||
nopropagation(); | ||
for (i = 0; i < n; ++i) { | ||
t = touches[i], p = touch(this, touches, t.identifier); | ||
p = [p, this.__zoom.invert(p), t.identifier]; | ||
if (!g.touch0) g.touch0 = p; | ||
else if (!g.touch1) g.touch1 = p; | ||
} | ||
if (touchstarting) { | ||
touchstarting = clearTimeout(touchstarting); | ||
if (!g.touch1) return g.end(), dblclicked.apply(this, arguments); | ||
} | ||
if (event.touches.length === n) { | ||
touchstarting = setTimeout(function() { touchstarting = null; }, touchDelay); | ||
interrupt(this); | ||
g.start(); | ||
} | ||
} | ||
function touchmoved() { | ||
// TODO | ||
var g = gesture(this, arguments), | ||
touches = event.changedTouches, | ||
n = touches.length, i, t, p, l; | ||
noevent(); | ||
if (touchstarting) touchstarting = clearTimeout(touchstarting); | ||
for (i = 0; i < n; ++i) { | ||
t = touches[i], p = touch(this, touches, t.identifier); | ||
if (g.touch0 && g.touch0[2] === t.identifier) g.touch0[0] = p; | ||
else if (g.touch1 && g.touch1[2] === t.identifier) g.touch1[0] = p; | ||
} | ||
t = g.that.__zoom; | ||
if (g.touch1) { | ||
var p0 = g.touch0[0], l0 = g.touch0[1], | ||
p1 = g.touch1[0], l1 = g.touch1[1], | ||
dp = (dp = p1[0] - p0[0]) * dp + (dp = p1[1] - p0[1]) * dp, | ||
dl = (dl = l1[0] - l0[0]) * dl + (dl = l1[1] - l0[1]) * dl; | ||
t = scale(t, Math.sqrt(dp / dl)); | ||
p = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2]; | ||
l = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2]; | ||
} | ||
else if (g.touch0) p = g.touch0[0], l = g.touch0[1]; | ||
else return; | ||
g.zoom("touch", translate(t, p, l)); | ||
} | ||
function touchended() { | ||
// TODO | ||
var g = gesture(this, arguments), | ||
touches = event.changedTouches, | ||
n = touches.length, i, t; | ||
nopropagation(); | ||
if (touchending) clearTimeout(touchending); | ||
touchending = setTimeout(function() { touchending = null; }, touchDelay); | ||
for (i = 0; i < n; ++i) { | ||
t = touches[i]; | ||
if (g.touch0 && g.touch0[2] === t.identifier) delete g.touch0; | ||
else if (g.touch1 && g.touch1[2] === t.identifier) delete g.touch1; | ||
} | ||
if (g.touch1 && !g.touch0) g.touch0 = g.touch1, delete g.touch1; | ||
if (!g.touch0) g.end(); | ||
} | ||
@@ -247,9 +346,5 @@ | ||
zoom.scaleExtent = function(_) { | ||
return arguments.length ? (scaleMin = +_[0], scaleMax = +_[1], zoom) : [scaleMin, scaleMax]; | ||
return arguments.length ? (k0 = +_[0], k1 = +_[1], zoom) : [k0, k1]; | ||
}; | ||
zoom.center = function(_) { | ||
return arguments.length ? (centerPoint = _ == null ? null : [+_[0], +_[1]], zoom) : centerPoint; | ||
}; | ||
zoom.duration = function(_) { | ||
@@ -256,0 +351,0 @@ return arguments.length ? (duration = +_, zoom) : duration; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
56089
14
767
267
5
+ Addedd3-drag@~0.2.0
+ Addedd3-drag@0.2.2(transitive)
- Removedd3-interpolate@0.7.0(transitive)
Updatedd3-interpolate@~0.8.0