跳至内容

d3-zoom

示例 · 平移和缩放 允许用户通过限制视图来关注感兴趣的区域。它使用直接操作:单击并拖动以平移(移动),滚动滚轮以缩放(比例),或用触控进行捏合。平移和缩放广泛用于基于 Web 的地图,但也可用于可视化,例如密集时间序列和散点图。

缩放行为是一个灵活的抽象,处理各种输入方式和浏览器怪癖。缩放行为与 DOM 无关,因此您可以将它与 HTML、SVGCanvas 一起使用。您可以将 d3-zoom 与 d3-scaled3-axis 一起使用来 缩放轴。您可以使用 zoom.scaleExtent 限制缩放,使用 zoom.translateExtent 限制平移。您可以将 d3-zoom 与其他行为相结合,例如 d3-drag 用于 拖动d3-brush 用于 焦点 + 上下文

缩放行为可以使用 zoom.transform 进行编程控制,允许您实现驱动显示的用户界面控件,或对数据进行 动画浏览平滑缩放过渡 基于 Jarke J. van Wijk 和 Wim A.A. Nuij 的 “平滑高效的缩放和平移”

另请参见 d3-tile,以获取平移和缩放地图的示例。

zoom()

源代码 · 创建一个新的缩放行为。返回的行为,zoom,是一个对象也是一个函数,通常通过 selection.call 应用于选定的元素。

zoom(selection)

源代码 · 将此缩放行为应用于指定的 selection,绑定必要的事件监听器以允许平移和缩放,并将每个选定元素的 缩放变换 初始化为恒等变换(如果尚未定义)。

此函数通常不会直接调用,而是通过 selection.call 调用。例如,要实例化一个缩放行为并将其应用于一个选择

js
selection.call(d3.zoom().on("zoom", zoomed));

在内部,缩放行为使用 selection.on 绑定缩放所需的事件监听器。监听器使用 .zoom 作为名称,因此您可以随后通过以下方式解除缩放行为的绑定

js
selection.on(".zoom", null);

要禁用仅由滚轮驱动的缩放(例如,不干扰本机滚动),您可以在将缩放行为应用于选择后删除缩放行为的滚轮事件监听器

js
selection
    .call(zoom)
    .on("wheel.zoom", null);

或者,使用 zoom.filter 来更好地控制哪些事件可以启动缩放手势。

应用缩放行为还会将 -webkit-tap-highlight-color 样式设置为透明,禁用 iOS 上的点击高亮显示。如果您想要不同的点击高亮显示颜色,请在应用拖动行为后删除或重新应用此样式。

缩放行为将缩放状态存储在应用了缩放行为的元素上,而不是存储在缩放行为本身。这允许缩放行为同时应用于多个元素,并具有独立的缩放功能。缩放状态可以因用户交互或通过 zoom.transform 以编程方式更改。

要检索缩放状态,请在缩放事件监听器(参见 zoom.on)中的当前 缩放事件 上使用 event.transform,或使用 zoomTransform 获取给定节点的缩放状态。后者对以编程方式修改缩放状态很有用,例如,实现用于放大和缩小的按钮。

zoom.transform(selection, transform, point)

源代码 · 如果 selection 是一个选择,则将选定元素的 当前缩放变换 设置为指定的 transform,并立即发出 start、zoom 和 end 事件

如果 selection 是一个过渡,则使用 interpolateZoom 定义一个“缩放”过渡到指定的 transform,在过渡开始时发出 start 事件,在过渡的每个刻度上发出 zoom 事件,然后在过渡结束(或中断)时发出 end 事件。过渡将尝试最大程度地减少指定 point 周围的视觉移动;如果未指定 point,则默认情况下为视口 范围 的中心。

transform 可以指定为 缩放变换 或返回缩放变换的函数;类似地,point 可以指定为一个两位数组 [x, y] 或返回此数组的函数。如果是一个函数,它将针对每个选定的元素调用,并将当前事件 (event) 和数据 d 传递给它,并将 this 上下文设置为当前 DOM 元素。

此函数通常不会直接调用,而是通过 selection.calltransition.call 调用。例如,要立即将缩放变换重置为 恒等变换

js
selection.call(zoom.transform, d3.zoomIdentity);

要以 750 毫秒的时长平滑地将缩放变换重置为恒等变换

js
selection.transition().duration(750).call(zoom.transform, d3.zoomIdentity);

此方法要求您完全指定新的缩放变换,并且不强制执行定义的 比例范围平移范围(如果有)。要从现有变换派生新的变换,并强制执行比例和平移范围,请参见便捷方法 zoom.translateByzoom.scaleByzoom.scaleTo

zoom.translateBy(selection, x, y)

源代码 · 如果 selection 是一个选择,则将选定元素的 当前缩放变换 平移 xy,使得新的 tx1 = tx0 + kxty1 = ty0 + ky。如果 selection 是一个过渡,则定义一个“缩放”过渡,平移当前变换。此方法是 zoom.transform 的便捷方法。xy 平移量可以指定为数字或返回数字的函数。如果是一个函数,它将针对每个选定的元素调用,并将当前数据 d 和索引 i 传递给它,并将 this 上下文设置为当前 DOM 元素。

zoom.translateTo(selection, x, y, p)

源代码 · 如果selection是一个选择器,则平移所选元素的当前缩放变换,使得给定位置⟨x,y⟩出现在给定点p处。新的tx = px - kxty = py - ky。如果未指定p,则默认值为视窗范围的中心。如果selection是一个过渡,则定义一个“缩放”过渡,平移当前变换。此方法是zoom.transform的便利方法。xy 坐标可以指定为数字或返回数字的函数;类似地,p 点可以指定为二元数组 [px,py] 或函数。如果为函数,则会为每个选定元素调用它,并传递当前数据d 和索引i,其中this 上下文为当前 DOM 元素。

zoom.scaleBy(selection, k, p)

源代码 · 如果selection是一个选择器,则将所选元素的当前缩放变换k缩放,使得新的k₁ = k₀k。参考点p不会移动。如果未指定p,则默认值为视窗范围的中心。如果selection是一个过渡,则定义一个“缩放”过渡,平移当前变换。此方法是zoom.transform的便利方法。k 缩放因子可以指定为数字或返回数字的函数;类似地,p 点可以指定为二元数组 [px,py] 或函数。如果为函数,则会为每个选定元素调用它,并传递当前数据d 和索引i,其中this 上下文为当前 DOM 元素。

zoom.scaleTo(selection, k, p)

源代码 · 如果selection是一个选择器,则将所选元素的当前缩放变换k缩放,使得新的k₁ = k。参考点p不会移动。如果未指定p,则默认值为视窗范围的中心。如果selection是一个过渡,则定义一个“缩放”过渡,平移当前变换。此方法是zoom.transform的便利方法。k 缩放因子可以指定为数字或返回数字的函数;类似地,p 点可以指定为二元数组 [px,py] 或函数。如果为函数,则会为每个选定元素调用它,并传递当前数据d 和索引i,其中this 上下文为当前 DOM 元素。

zoom.constrain(constrain)

源代码 · 如果指定了constrain,则将变换约束函数设置为指定的函数并返回缩放行为。如果未指定constrain,则返回当前约束函数,该函数默认为

js
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)
  );
}

约束函数必须返回一个 [transform]#zoomTransform),该函数基于当前的transform视窗范围平移范围。默认实现尝试确保视窗范围不超出平移范围。

zoom.filter(filter)

源代码 · 如果指定了filter,则将过滤器设置为指定的函数并返回缩放行为。如果未指定filter,则返回当前过滤器,该过滤器默认为

js
function filter(event) {
  return (!event.ctrlKey || event.type === 'wheel') && !event.button;
}

过滤器传递当前事件 (event) 和数据 d,其中this 上下文为当前 DOM 元素。如果过滤器返回假值,则会忽略启动事件,并且不会启动任何缩放手势。因此,过滤器决定哪些输入事件会被忽略。默认过滤器会忽略次级按钮上的鼠标按下事件,因为这些按钮通常用于其他目的,例如上下文菜单。

zoom.touchable(touchable)

源代码 · 如果指定了touchable,则将触摸支持检测器设置为指定的函数并返回缩放行为。如果未指定touchable,则返回当前触摸支持检测器,该检测器默认为

js
function touchable() {
  return navigator.maxTouchPoints || ("ontouchstart" in this);
}

只有当检测器针对相应元素返回真值时,才会注册触摸事件监听器,前提是缩放行为已应用。默认检测器适用于大多数支持触摸输入的浏览器,但并非所有浏览器都适用;例如,Chrome 的移动设备模拟器无法检测到。

zoom.wheelDelta(delta)

源代码 · 如果指定了delta,则将滚轮增量函数设置为指定的函数并返回缩放行为。如果未指定delta,则返回当前滚轮增量函数,该函数默认为

js
function wheelDelta(event) {
  return -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * (event.ctrlKey ? 10 : 1);
}

滚轮增量函数返回的值Δ 决定了响应WheelEvent 应用的缩放量。缩放因子transform.k 乘以 2Δ;例如,Δ 为 +1 将缩放因子翻倍,Δ 为 -1 将缩放因子减半。

zoom.extent(extent)

源代码 · 如果指定了extent,则将视窗范围设置为指定的一组点 [[x0, y0], [x1, y1]],其中 [x0, y0] 为视窗的左上角,[x1, y1] 为视窗的右下角,并返回此缩放行为。extent 也可以指定为返回此类数组的函数;如果为函数,则会为每个选定元素调用它,并传递当前数据 d,其中this 上下文为当前 DOM 元素。

如果未指定extent,则返回当前范围访问器,该访问器默认为 [[0, 0], [width, height]],其中 width 为元素的客户端宽度height 为元素的客户端高度;对于 SVG 元素,将使用最近的祖先 SVG 元素的视窗或 widthheight 属性。或者,请考虑使用element.getBoundingClientRect

视窗范围会影响多个函数:在 zoom.scaleByzoom.scaleTo 进行更改时,视窗的中心保持固定;视窗中心和尺寸会影响interpolateZoom 选择的路径;并且需要视窗范围才能强制执行可选的平移范围

zoom.scaleExtent(extent)

源代码 · 如果指定了extent,则将缩放范围设置为指定的一组数字 [k0, k1],其中 k0 为允许的最小缩放因子,k1 为允许的最大缩放因子,并返回此缩放行为。如果未指定extent,则返回当前缩放范围,该范围默认为 [0, ∞]。缩放范围限制放大和缩小。它在交互和使用 zoom.scaleByzoom.scaleTozoom.translateBy 时被强制执行;但是,在使用 zoom.transform 显式设置变换时,它不会被强制执行。

如果用户在已达到缩放范围对应限制时尝试通过滚轮进行缩放,则会忽略滚轮事件,并且不会启动缩放手势。这允许用户在放大后向下滚动到可缩放区域之外,或者在缩小后向上滚动。如果您希望始终阻止滚轮输入时的滚动,无论缩放范围如何,请注册滚轮事件监听器以阻止浏览器默认行为

js
selection
    .call(zoom)
    .on("wheel", event => event.preventDefault());

zoom.translateExtent(extent)

源代码 · 如果指定了extent,则将平移范围设置为指定的一组点 [[x0, y0], [x1, y1]],其中 [x0, y0] 为世界的左上角,[x1, y1] 为世界的右下角,并返回此缩放行为。如果未指定extent,则返回当前平移范围,该范围默认为 [[-∞, -∞], [+∞, +∞]]。平移范围限制平移,并可能在缩小时导致平移。它在交互和使用 zoom.scaleByzoom.scaleTozoom.translateBy 时被强制执行;但是,在使用 zoom.transform 显式设置变换时,它不会被强制执行。

zoom.clickDistance(distance)

源代码 · 如果指定了distance,则设置鼠标在鼠标按下和鼠标松开之间可以移动的最大距离,这将触发随后的单击事件。如果在鼠标按下和鼠标松开之间的任何时间点,鼠标距离其在鼠标按下时的位置大于或等于distance,则在鼠标松开后的单击事件将被抑制。如果未指定distance,则返回当前距离阈值,该阈值默认为零。距离阈值以客户端坐标衡量 (event.clientXevent.clientY)。

zoom.tapDistance(distance)

源代码 · 如果指定了distance,则设置双击手势在第一次触摸开始和第二次触摸结束之间可以移动的最大距离,这将触发随后的双击事件。如果未指定distance,则返回当前距离阈值,该阈值默认为 10。距离阈值以客户端坐标衡量 (event.clientXevent.clientY)。

zoom.duration(duration)

源代码 · 如果指定了duration,则将双击和双点触控时的缩放过渡的持续时间设置为指定的毫秒数,并返回缩放行为。如果未指定duration,则返回当前持续时间,该持续时间默认为 250 毫秒。如果持续时间不大于零,则双击和点触控会触发对缩放变换的瞬时更改,而不是启动平滑过渡。

要禁用双击和双点触控转换,您可以在将缩放行为应用于选择后移除缩放行为的 dblclick 事件监听器。

js
selection
    .call(zoom)
    .on("dblclick.zoom", null);

zoom.interpolate(interpolate)

源代码 · 如果指定了 interpolate,则将缩放转换的插值工厂设置为指定的函数。如果未指定 interpolate,则返回当前的插值工厂,默认情况下为 interpolateZoom 以实现平滑缩放。要应用两个视图之间的直接插值,请尝试 interpolate

zoom.on(typenames, listener)

源代码 · 如果指定了 listener,则为指定的 typenames 设置事件 listener 并返回缩放行为。如果已经为相同类型和名称注册了事件监听器,则在添加新监听器之前会移除现有监听器。如果 listener 为 null,则移除为指定的 typenames 注册的当前事件监听器(如果有)。如果未指定 listener,则返回第一个当前分配的与指定的 typenames 匹配的监听器(如果有)。当分发指定事件时,每个 listener 将使用与 selection.on 监听器相同的上下文和参数调用:当前事件 (event) 和数据 d,其中 this 上下文为当前 DOM 元素。

typenames 是一个字符串,包含一个或多个以空格分隔的 typename。每个 typename 都是一个 type,可以选择后跟一个句点 (.) 和一个 name,例如 zoom.foozoom.bar;该名称允许为同一 type 注册多个监听器。type 必须是以下之一:

  • start - 缩放开始后(例如,在 mousedown 时)。
  • zoom - 缩放变换更改后(例如,在 mousemove 时)。
  • end - 缩放结束时(例如,在 mouseup 时)。

有关详细信息,请参见 dispatch.on

缩放事件

当调用 缩放事件监听器 时,它将接收当前缩放事件作为第一个参数。event 对象公开以下几个字段:

  • event.target - 关联的 缩放行为
  • event.type - 字符串“start”、“zoom”或“end”;请参见 zoom.on
  • event.transform - 当前的 缩放变换
  • event.sourceEvent - 底层输入事件,例如 mousemove 或 touchmove。

缩放行为处理各种交互事件

事件监听元素缩放事件默认阻止?
mousedown⁵选择start否¹
mousemove²窗口¹zoom
mouseup²窗口¹end
dragstart²窗口-
selectstart²窗口-
click³窗口-
dblclick选择multiple
wheel⁸选择zoom⁷
touchstart选择multiple否⁴
touchmove选择zoom
touchend选择end否⁴
touchcancel选择end否⁴

所有消耗事件的传播都将 立即停止

¹ 为了捕获 iframe 外部的事件而必须使用;请参见 d3-drag#9
² 仅在处于活动状态的基于鼠标的手势期间适用;请参见 d3-drag#9
³ 仅在某些基于鼠标的手势之后立即适用;请参见 zoom.clickDistance
⁴ 为了允许 点击模拟 在触摸输入上;请参见 d3-drag#9
⁵ 如果在触摸手势结束后的 500 毫秒内发生,则会忽略;假设 点击模拟
⁶ 双击和双点触控会启动一个转换,该转换会发出 start、zoom 和 end 事件;请参见 zoom.tapDistance
⁷ 第一个 wheel 事件会发出 start 事件;如果 150 毫秒内没有收到 wheel 事件,则会发出 end 事件。
⁸ 如果已经达到 缩放范围 的相应限制,则会忽略。

zoomTransform(node)

源代码 · 返回指定 node 的当前变换。请注意,node 通常应为 DOM 元素,而不是 selection。(选择可能包含多个节点,处于不同的状态,而此函数仅返回单个变换。)如果您有选择,请先调用 selection.node

js
var transform = d3.zoomTransform(selection.node());

事件监听器 的上下文中,node 通常是接收输入事件的元素(应等于 event.transform),this

js
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.scaletransform.translate 推导出新的变换。另请参见 zoom.scaleByzoom.scaleTozoom.translateBy,它们是缩放行为上的便捷方法。要创建具有给定 ktxty 的变换,请执行以下操作:

js
var t = d3.zoomIdentity.translate(x, y).scale(k);

要将变换应用于 Canvas 2D 上下文,请使用 context.translate,然后使用 context.scale

js
context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);

类似地,要通过 CSS 将变换应用于 HTML 元素,请执行以下操作:

js
div.style("transform", "translate(" + transform.x + "px," + transform.y + "px) scale(" + transform.k + ")");
div.style("transform-origin", "0 0");

要将变换应用于 SVG,请执行以下操作:

js
g.attr("transform", "translate(" + transform.x + "," + transform.y + ") scale(" + transform.k + ")");

或者更简单地,利用 transform.toString

js
g.attr("transform", transform);

请注意,变换的顺序很重要!平移必须在缩放之前应用。

zoomIdentity

源代码 · 单位变换,其中 k = 1,tx = ty = 0。

new d3.ZoomTransform(k, x, y)

源代码 · 返回具有缩放比例 k 和平移 (x, y) 的变换。

transform.scale(k)

源代码 · 返回一个变换,其缩放比例 k₁ 等于 k₀k,其中 k₀ 是此变换的缩放比例。

transform.translate(x, y)

源代码 · 返回一个变换,其平移量 tx1ty1 等于 tx0 + tk xty0 + tk y,其中 tx0ty0 是此变换的平移量,tk 是此变换的缩放比例。

transform.apply(point)

源代码 · 返回指定 point 的变换,该 point 是一个包含两个数字 [x, y] 的数组。返回的点等于 [xk + tx, yk + ty]。

transform.applyX(x)

源代码 · 返回指定 x 坐标的变换,xk + tx

transform.applyY(y)

源代码 · 返回指定 y 坐标的变换,yk + ty

transform.invert(point)

源代码 · 返回指定 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,其 被转换。这是通过首先对比例的 范围 应用 x 变换,然后应用 逆比例 来计算相应的域来实现的

js
function rescaleX(x) {
  var range = x.range().map(transform.invertX, transform),
      domain = range.map(x.invert, x);
  return x.copy().domain(domain);
}

比例尺x必须使用interpolateNumber;不要使用continuous.rangeRound,因为这会降低continuous.invert的精度,并可能导致重新缩放域不准确。此方法不会修改输入比例尺x;因此,x表示未转换的比例尺,而返回的比例尺表示其转换后的视图。

transform.rescaleY(y)

来源 · 返回副本连续比例尺y,其已转换。这是通过首先将反向y转换应用于比例尺的范围,然后应用反向比例尺来计算相应的域来实现的

js
function rescaleY(y) {
  var range = y.range().map(transform.invertY, transform),
      domain = range.map(y.invert, y);
  return y.copy().domain(domain);
}

比例尺y必须使用interpolateNumber;不要使用continuous.rangeRound,因为这会降低continuous.invert的精度,并可能导致重新缩放域不准确。此方法不会修改输入比例尺y;因此,y表示未转换的比例尺,而返回的比例尺表示其转换后的视图。

transform.toString()

来源 · 返回一个字符串,表示与此转换相对应的SVG 转换。实现为

js
function toString() {
  return "translate(" + this.x + "," + this.y + ") scale(" + this.k + ")";
}