d3-zoom
平移和缩放是流行的交互技术,其通过限制视图让用户专注于感兴趣的区域。由于直接操作而易于学习:单击并拖动进行平移(translate),旋转滚轮或使用触摸进行缩放(scale)。平移和缩放广泛用于基于Web的映射,但也可用于可视化,如时间序列和散点图。
由d3-zoom实现的缩放行为是一种方便但灵活的抽象,用于启用对选集的平移和缩放。它能处理令人惊讶的各种输入事件和浏览器怪癖。缩放行为与DOM无关,因此你可以将其与SVG,HTML或Canvas结合使用。
缩放行为也设计用于与d3-scale和d3-axis协同使用;另请参阅transform.rescaleX和transform.rescaleY。你也可以使用zoom.scaleExtent限制缩放,或使用zoom.translateExtent限制平移。
缩放行为也可以和其它行为结合使用,如用于拖动的d3-drag,用于聚焦和上下文的d3-brush。
缩放行为可以通过使用zoom.transform以编程方式进行控制,从而允许你实现用户界面控件来驱动显示或通过数据对动画导览进行分级。平滑缩放过渡基于Jarke J. van Wijk和Wim AA Nuij的“Smooth and efficient zooming and panning”。
Installing
如果你使用npm,请键入npm install d3-zoom
。否则,请下载最新版本。你也可以直接从d3js.org加载,作为standalone library(独立库)或D3 4.0的一部分来使用。支持AMD,CommonJS和vanilla环境。在vanilla环境下,会输出一个全局的d3
:
<script src="https://d3js.org/d3-color.v1.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-ease.v1.min.js"></script>
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="https://d3js.org/d3-timer.v1.min.js"></script>
<script src="https://d3js.org/d3-transition.v1.min.js"></script>
<script src="https://d3js.org/d3-drag.v1.min.js"></script>
<script src="https://d3js.org/d3-zoom.v1.min.js"></script>
<script>
var zoom = d3.zoom();
</script>
API Reference
此表描述了缩放行为如何插入原生事件:
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⁴ |
所有事件的传播都被立即停止。
¹ 需要在iframe之外捕获事件;请参阅d3-drag#9。
² 只在基于鼠标的活动手势中应用;请参阅d3-drag#9。
³ 只在某些基于鼠标的手势后立即应用;请参阅zoom.clickDistance。
⁵ 如果触摸手势结束少于500毫秒则忽略;假设点击仿真。
⁶ 双击初始化一个过渡,触发start,zoom和end事件。
⁷ 第一次滚轮事件触发start事件;当150毫秒内没有滚轮事件时触发end事件。
⁸ 如果已经在缩放范围内则忽略。
d3.zoom()
创建一个新的缩放行为。返回的行为,zoom,既是一个对象也是一个函数,且通常通过selection.call应用于选定元素。
zoom(selection)
将此缩放行为应用于给定selection,绑定必要的事件监听器以允许平移和缩放,以及如果尚未定义,则将每个选定元素上的缩放转换初始化为恒等转换。通常不会直接调用此函数,而是通过selection.call来调用。例如,要实例化缩放行为并将其应用于选集:
selection.call(d3.zoom().on("zoom", zoomed));
在内部,缩放行为使用selection.on绑定必要的事件监听器进行缩放。监听器使用名称.zoom
,因此你可以随后解除该缩放行为,如下所示:
selection.on(".zoom", null);
要禁用滚轮个驱动缩放(比如说不干扰本地滚动),可以在将缩放行为应用到选集后移除缩放行为的wheel事件监听器:
selection
.call(zoom)
.on("wheel.zoom", null);
另外,使用zoom.filter可更好地控制,其可以启动缩放手势。
应用缩放行为还会将-webkit-tap-highlight-color设置为透明,从而禁用iOS上的点击突出显示。如果你需要不同的突出显示颜色,请在应用拖动行为后移除或重新应用此样式。
zoom.transform(selection, transform)
如果selection为选集,则设置选定元素当前的缩放转换为给定的transform,瞬间触发start,zoom和end事件。如果selection为过渡,则使用d3.interpolateZoom为给定transform定义一个“zoom”补间,当过渡开始时触发start事件,过渡的每一刻触发zoom事件,最后在过渡结束(或中断)时触发end事件。transform可以指定为缩放转换或返回缩放转换的函数。如果为函数,则为每个选定元素调用,传入当前数据d
及索引i
,并将this
上下文作为当前的DOM元素。
此函数通常不会直接调用,而是通过selection.call或transition.call调用。例如,要立即将缩放转换重置为恒等转换:
selection.call(zoom.transform, d3.zoomIdentity);
要在750毫秒内平滑地将缩放转换重置为标识转换:
selection.transition().duration(750).call(zoom.transform, d3.zoomIdentity);
此方法要求你完全指定新的缩放转换,并且不强制定义的缩放范围和平移范围(如果有的话)。要从现有转换中派生新转换,并执行缩放和平移范围,请参阅便捷方法zoom.translateBy,zoom.scaleBy和zoom.scaleTo。
zoom.translateBy(selection, x, y)
如果selection为选集,则通过x和y平移选定元素当前的缩放转换,使得新的Tx1 = Tx0 + Kx,Ty1 = Ty0 + Ky 。如果selection为函数,则为当前转换定义一个“zoom”补间。此方法为zoom.transform的简便方法。平移量x和y可以指定为数字或返回数字的函数。如果为函数,则为每个选定元素调用,传入当前的数据d
及索引i
,并将this
上下文作为当前的DOM元素。
zoom.translateTo(selection, x, y)
如果selection为选集,则平移选定元素当前的缩放转换,使得给定位置⟨x,y⟩显示在视图范围的中心。新的Tx = Cx - Kx和Ty = Cy - Ky中,⟨Cx,Cy⟩即为中心。如果selection为过渡,则为当前转换定义一个“zoom”补间。此方法为zoom.transform的简便方法。横坐标x和纵坐标y可以指定为数字或返回数字的函数。如果为函数,则为每个选定元素调用,传入当前的数据d
及索引i
,并将this
上下文作为当前的DOM元素。
zoom.scaleBy(selection, k)
如果selection为选集,则通过k缩放选定元素当前的缩放转换,使得k₁ = k₀k。如果selection为过渡,则为当前转换定义一个“zoom”补间。此方法为zoom.transform的简便方法。缩放因子k可以指定为数字或返回数字的函数。如果为函数,则为每个选定元素调用,传入当前的数据d
及索引i
,并将this
上下文作为当前的DOM元素。
zoom.scaleTo(selection, k)
如果selection为选集,则将选定元素当前的缩放转换缩放至k,使得k₁ = k。如果selection为过渡,则为当前转换定义一个“zoom”补间。此方法为zoom.transform的简便方法。缩放因子k可以指定为数字或返回数字的函数。如果为函数,则为每个选定元素调用,传入当前的数据d
及索引i
,并将this
上下文作为当前的DOM元素。
zoom.constrain([constrain])
如果指定了constrain,则设置转换约束函数为给定函数并返回此缩放行为。如果不指定constrain,则返回当前的约束函数,默认为:
function constrain(transform, extent, translateExtent) {
var dx0 = transform.invertX(extent[0][0]) - translateExtent[0][0],
dx1 = transform.invertX(extent[1][0]) - translateExtent[1][0],
dy0 = transform.invertY(extent[0][1]) - translateExtent[0][1],
dy1 = transform.invertY(extent[1][1]) - translateExtent[1][1];
return transform.translate(
dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1),
dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1)
);
}
约束函数必须返回一个转换,给出当前的转换、视口范围和平移范围。默认实现尝试确保视口范围不会超出平移范围。
zoom.filter([filter])
如果指定了filter,设置过滤器为给定函数并返回此缩放行为。如果不指定filter,则返回当前过滤器,默认为:
function filter() {
return !d3.event.button;
}
如果过滤器返回false,则初始化事件将被忽略,并且不会启动缩放手势。因此,过滤器决定忽略哪些输入事件。默认过滤器会忽略辅助按钮上的mousedown事件,因为这些按钮通常用于其他用途,例如上下文菜单。
zoom.touchable([touchable])
如果指定了touchable,则设置触摸支持检测器为给定的函数并返回此缩放行为。如果不指定touchable,则返回当前的触摸支持检测器,默认为:
function touchable() {
return "ontouchstart" in this;
}
触摸事件侦听器仅在检测器在应用缩放行为时对相应元素返回true时才被注册。默认检测器适用于大多数能够触摸输入的浏览器,但不是全部;;例如,Chrome的移动设备模拟器检测失败。
zoom.wheelDelta([delta])
如果指定了delta,则设置滚轮delta函数为给定函数并返回此缩放行为。如果不指定delta,则返回当前的滚轮delta函数,默认为:
function wheelDelta() {
return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1) / 500;
}
滚轮delta函数返回的值Δ决定了响应WheelEvent的缩放应用量。缩放因子transform.k乘以了2的Δ次方;例如,Δ为+1时缩放因子翻倍,Δ为-1时缩放因子减半。
zoom.extent([extent])
如果指定了extent,则设置视口范围为给定的点数组[[x0, y0], [x1, y1]],其中[x0, y0]为视口的左上角[x1, y1]为视口的右下角,并返回此缩放行为。extent也可以指定为返回数组的函数;如果为函数,则为每个选定元素调用,传入当前的数据d
及索引i
,并将this
上下文作为当前的DOM元素。
如果不指定extent,则范围当前的extent accessor,默认为[[0, 0], [width, height]],其中width为元素的client width,而height为元素的client height;对于SVG元素,则使用最近祖先SVG元素的width和height。在这种情况下,所有者SVG元素必须具有已定义的width和height属性,而不是(例如)依赖于CSS属性或viewBox属性;SVG不提供用于检索初始视口大小的编程方法。或者,可以考虑使用element.getBoundingClientRect。(在火狐浏览器中,SVG元素的element.clientWidth和element.clientHeight为0!)
视口范围影响着几个函数:在zoom.scaleBy和zoom.scaleTo变化时视口中保持固定;视口中心和尺寸会影响d3.interpolateZoom选择的路径;执行可选的平移范围时需要视口范围。
zoom.scaleExtent([extent])
如果指定了extent,则设置缩放范围为给定的数字数组[k0, k1],其中k0为最小允许缩放因子,而k1为最大允许缩放因子,并返回此缩放行为。如果不指定extent,则返回当前的缩放范围,默认为[0, ∞]。缩放范围约束着放大和缩小。它在交互及使用zoom.scaleBy、zoom.scaleTo和zoom.translateBy时执行;但是,它在使用zoom.transform显式设置转换时不会执行。
如果用户在已经处于相应的缩放范围限制时试图通过滚轮进行缩放,则wheel事件将被忽略并且不会启动缩放手势。这允许用户在放大之后向下滚动经过可缩放区域,或者在缩小之后向上滚动。如果你希望始终防止滚轮输入滚动,而不考虑缩放范围,请注册wheel事件监听器以防止浏览器默认行为:
selection
.call(zoom)
.on("wheel", function() { d3.event.preventDefault(); });
zoom.translateExtent([extent])
如果指定了extent,则设置平移范围为给定点数组[[x0, y0], [x1, y1]],其中[x0, y0]为世界的左上角,而[x1, y1]为世界的右下角,并返回此缩放行为。如果不指定extent,则返回当前平移范围,默认为[[-∞, -∞], [+∞, +∞]]。平移范围约束着平移,并可能在缩小时产生导致平移。它在交互及使用zoom.scaleBy、zoom.scaleTo和zoom.translateBy时执行;但是,它在使用zoom.transform显式设置转换时不会执行。
zoom.clickDistance([distance])
如果指定了distance,则设置鼠标在mousedown和mouseup之间能够触发后续click事件的能移动的最大距离。如果在mousedown和mouseup之间的任何点鼠标大于或等于它在mousedown上的位置的distance,mouseup后面的click事件将被抑制。如果不指定distance,则返回当前的距离阈值,默认为0。距离阈值在客户端坐标系(event.clientX和event.clientY)中测量。
zoom.duration([duration])
如果指定了duration,则设置双击时缩放过渡的持续时间为给定数字(并以毫秒为单位),并返回此缩放行为。如果不指定duration,则返回当前的持续时间,默认为250毫秒。如果持续时间大于0,双击将立即触发缩放转换而不是启动平滑过渡。
想要双击过渡,你可以在将缩放行为应用到选集后移除缩放行为的dblclick事件监听器:
selection
.call(zoom)
.on("dblclick.zoom", null);
zoom.interpolate([interpolate])
如果指定了interpolate,则设置缩放过渡的插值器工厂函数为给定的函数。如果不指定interpolate,则返回当前的插值器工厂函数,默认为d3.interpolateZoom以实现平滑缩放。要在两个视图之间应用直接插值,请尝试使用d3.interpolate。
zoom.on(typenames[, listener])
如果指定了listener,则为给定typenames设置给定的listener并返回此缩放行为。如果已经注册了相同类型和名称的事件监听器,则会在添加新的事件监听器前先移除已有的监听器。如果listener为null,则移除给定typenames的当前事件监听器(如果有的话)。如果不指定listener,则返回匹配给定typenames的第一个当前分配的监听器(如果有的话)。当指定事件派发时,每个listener都会被调用,并传入与selection.on监听器相同的上下文和参数:当前数据d
及索引i
,并将this
上下文作为当前的DOM元素。
typenames为包含一个或多个由空格分隔的typename。每个typename都是一个type,可以选择跟一个句点(.
)和一个name,如zoom.foo
和zoom.bar
,该名称允许为同一type注册多个监听器。type必须是下列之一:
start
- 缩放开始后(如mousedown)。zoom
- 缩放转换改变后(如mousemove)。end
- 缩放结束后(如mouseup)。
另请参阅dispatch.on了解更多。
Zoom Events
当缩放事件监听器被调用时,d3.event会设置为当前的缩放事件。event对象会暴露一些字段:
- event.target - 相关联的缩放行为。
- event.type - 字符串“start”,“zoom”或“end”;另请参阅zoom.on。
- event.transform - 当前缩放转换。
- event.sourceEvent - 底层输入事件,如mousemove或touchmove。
Zoom Transforms
缩放行为将缩放状态存储在应用缩放行为的元素上,而不是缩放行为本身。这是因为缩放行为可以同时应用于多个元素,并且每个元素可以独立缩放。缩放状态可以改变用户交互或通过zoom.transform编程。
要检索缩放状态,请在缩放事件侦听器(请参阅zoom .on)中的当前缩放事件上使用event .transform ,或者对给定节点使用d3.zoomTransform。后者对于以编程方式修改缩放状态特别有用,比如实现用于放大和缩小的按钮。
d3.zoomTransform(node)
返回给定node的当前转换。注意,node通常应该是DOM元素,而不是选集。(一个选集可能包含多个节点,处于不同的状态,而此函数只返回单一转换。)如果你有一个选集,请首先调用selection.node:
var transform = d3.zoomTransform(selection.node());
在事件监听器的上下文中,node通常是接收输入事件的元素(它应该等于event.transform),如下所示:
var transform = d3.zoomTransform(this);
在内部,元素的变换存储为element.__ zoom;;但是,你应该使用此方法而不是直接访问它。如果给定node没有定义的转换,则返回恒等转换。返回的转换表示为二维转换矩阵形式:
k 0 tx
0 k ty
0 0 1
(此矩阵只能够表示缩放和平移;将来的版本还可以允许旋转,尽管这可能不会是向后兼容的改变。)位置⟨x,y⟩转换为了⟨xk + tx,yk + ty⟩。转换对象暴露出以下属性:
- transform.x - 沿x轴的平移量tx。
- transform.y - 沿y轴的平移量ty。
- transform.k - 缩放因子k。
这些属性应该是只读的;而不是用于修改一个转换,使用transform.scale和transform.translate来得到一个新的转换。另请参阅zoom.scaleBy、zoom.scaleTo和zoom.translateBy了解缩放行为的简便方法。
要使用给定的k,tx和ty创建一个转换:
var t = d3.zoomIdentity.translate(x, y).scale(k);
要将转换应用到Canvas 2D context,请使用context.translate,然后使用context.scale:
context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);
同样,要通过CSS将转换应用到HTML元素:
div.style("transform", "translate(" + transform.x + "px," + transform.y + "px) scale(" + transform.k + ")");
div.style("transform-origin", "0 0");
将转换应用到SVG:
g.attr("transform", "translate(" + transform.x + "," + transform.y + ") scale(" + transform.k + ")");
或者更简单地说,利用transform.toString:
g.attr("transform", transform);
请注意,转换的顺序很重要!平移必须在缩放之前应用。
transform.scale(k)
返回一个转换,其缩放k₁等于k₀k,而k₀为此转换的缩放。
transform.translate(x, y)
返回一个转换,其平移tx1和ty1等于tx0 + x和ty0 + y,而tx0和ty0为此转换的平移。
transform.apply(point)
返回给定point[x, y]的转换,该点为仅有两个元素的数字数组。返回的点等于[xk + tx, yk + ty]。
transform.applyX(x)
返回给定x坐标的转换,xk + tx。
transform.applyY(y)
返回给定y坐标的转换,yk + ty。
transform.invert(point)
返回给定point[x, y]的反向转换,该点为仅有两个元素的数字数组。返回的点等于[(x - tx) / k, (y - ty) / k]。
transform.invertX(x)
返回给定x坐标的反向转换,(x - tx) / k。
transform.invertY(y)
返回给定y坐标的反向转换,(y - ty) / k。
transform.rescaleX(x)
返回连续比例尺x的一个副本,其domain已经过转换。此实现通过首先对比例尺的range应用反转x转换,然后应用反转比例尺来计算对应的domain:
function rescaleX(x) {
var range = x.range().map(transform.invertX, transform),
domain = range.map(x.invert, x);
return x.copy().domain(domain);
}
比例尺x必须使用d3.interpolateNumber;不要使用continuous.rangeRound,因为它会减小continuous.invert的精度,并可能导致不精确的rescaled domain。此方法不会修改输入比例尺x;因此x表示未经转换的比例尺,而返回的比例尺表示其准换后的视图。
transform.rescaleY(y)
返回连续比例尺y的一个副本,其domain已经过转换。此实现通过首先对比例尺的range应用反转y转换,然后应用反转比例尺来计算对应的domain:
function rescaleY(y) {
var range = y.range().map(transform.invertY, transform),
domain = range.map(y.invert, y);
return y.copy().domain(domain);
}
比例尺y必须使用d3.interpolateNumber;不要使用continuous.rangeRound,因为它会减小continuous.invert的精度,并可能导致不精确的rescaled domain。此方法不会修改输入比例尺y;因此y表示未经转换的比例尺,而返回的比例尺表示其准换后的视图。
transform.toString()
返回表示与此转换对应的SVG转换的字符串。其实现为:
function toString() {
return "translate(" + this.x + "," + this.y + ") scale(" + this.k + ")";
}
d3.zoomIdentity
恒等转换,其k = 1, tx = ty = 0。