Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

d3-zoom

Package Overview
Dependencies
Maintainers
1
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

d3-zoom - npm Package Compare versions

Comparing version 0.0.2 to 0.1.0

src/noevent.js

334

build/d3-zoom.js
(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"

@@ -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;
}
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;

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc