
如何用 scaleExtent 和 translateExtent 限制 d3-zoom 的缩放与平移范围【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3在 SVG、HTML 或 Canvas 可视化中用 d3 的 zoom behavior 做平移和缩放时默认情况下缩放系数没有边界默认值[0, ∞]世界范围也是无限大默认值[[-∞, -∞], [∞, ∞]]。也就是说用户可以不断缩小直到图形缩成一个点也可以把视野平移到完全没有内容的空白区域。*zoom*.scaleExtent和*zoom*.translateExtent是 docs/d3-zoom.md 中针对这两个问题的配置入口前者限定允许的缩放系数区间后者限定平移可达的世界范围。本文以一个可以直接运行的空白图表为例说明如何给 zoom behavior 配置这两个限制、如何验证限制生效以及哪些程序化调用会执行限制、哪些会绕过限制。准备环境与加载 D3D3 works in any JavaScript environmentD3 可以在任意 JavaScript 环境运行。本仓库package.json中 D3 版本为 7.9.0依赖d3-zoom: ^3.0.0要求 Node12。在浏览器页面中文档推荐的加载方式是 CDN 提供的 ES module 包如果你使用 Node 工程yarn、npm、pnpm 均可则用包管理器安装后import * as d3 from d3。下面主路径使用 CDN ESM 方式单文件即可运行。构建图表并配置两个限制下面的完整示例基于 docs/getting-started.md 中的空白图表改出先画好带坐标轴的 SVG再把 zoom behavior 应用到 SVG 上。运行前只需替换一处把K0和K1换成你的图表允许的最小、最大缩放系数文档对 scaleExtent 的定义是k0为最小允许缩放系数k1为最大允许缩放系数不设置时默认为[0, ∞]。其余代码可直接复制运行。!DOCTYPE html div idcontainer/div script typemodule import * as d3 from https://cdn.jsdelivr.net/npm/d37/esm; // Declare the chart dimensions and margins. const width 640; const height 400; const marginTop 20; const marginRight 20; const marginBottom 30; const marginLeft 40; // Declare the x (horizontal position) scale. const x d3.scaleUtc() .domain([new Date(2023-01-01), new Date(2024-01-01)]) .range([marginLeft, width - marginRight]); // Declare the y (vertical position) scale. const y d3.scaleLinear() .domain([0, 100]) .range([height - marginBottom, marginTop]); // Create the SVG container. const svg d3.create(svg) .attr(width, width) .attr(height, height); // 图表内容放进一个独立的 group缩放变换只作用于它坐标轴保持在原位。 const g svg.append(g); // Add the x-axis. svg.append(g) .attr(transform, translate(0,${height - marginBottom})) .call(d3.axisBottom(x)); // Add the y-axis. svg.append(g) .attr(transform, translate(${marginLeft},0)) .call(d3.axisLeft(y)); // 配置 zoom behavior // - scaleExtent([K0, K1])限定缩放系数范围替换为你自己的最小/最大值 // - translateExtent把世界限定为图表自身的绘图区域禁止平移到空白处 const zoom d3.zoom() .scaleExtent([K0, K1]) .translateExtent([[0, 0], [width, height]]) .on(zoom, event { g.attr(transform, event.transform); }); // 把 zoom behavior 应用到 SVG 元素上。 svg.call(zoom); // Append the SVG element. container.append(svg.node()); /script这段代码里的几个关键点和文档一一对应d3.zoom()创建一个新的 zoom behavior返回的 behavior 既是对象也是函数通常通过selection.call(...)应用到选中的元素上见 docs/d3-zoom.md 的zoom()与*zoom*(*selection*)两节。应用后每个被选中元素上的 zoom transform 会被初始化为 identity transform之后由用户交互或程序调用改变。.on(zoom, ...)的回调在每次 zoom transform 变化时被调用回调收到的事件对象上event.transform就是当前的 zoom transform。文档给出了把它写到 SVG group 的简写形式g.attr(transform, transform)transform 对象的toString会输出translate(x,y) scale(k)形式的 SVG 变换字符串且平移在前、缩放在后顺序由它保证。translateExtent([[0, 0], [width, height]])中width、height就是本例声明的 640 和 400即把世界边界设为图表自身范围。文档对translateExtent的定义是[x0, y0]为世界左上角、[x1, y1]为世界右下角。两个限制的语义与生效范围*zoom*.scaleExtent(*extent*)**设置为数组[k0, k1]限制放大和缩小。它在**用户交互**以及调用zoom.scaleBy、zoom.scaleTo、zoom.translateBy时强制执行但在通过zoom.transform 显式设置 transform 时不执行**。***zoom*.translateExtent(*extent*)**设置为两个点[[x0, y0], [x1, y1]]限制平移缩放缩小时也可能引起额外的平移修正。生效范围与 scaleExtent 相同交互和scaleBy/scaleTo/translateBy执行zoom.transform 不执行。另外两个相关概念决定了 translateExtent 实际如何起作用视口 extent*zoom*.extent文档明确说明执行 translate extent 需要视口 extent。它默认为[[0, 0], [width, height]]取自所应用元素的 client 宽高对 SVG 元素则取其最近的祖先 SVG 元素的 viewBox 或width、height属性。本例中 SVG 显式声明了width、height默认视口 extent 即为[[0, 0], [640, 400]]与translateExtent一致无需显式设置。若你的 SVG 依赖 viewBox 缩放且两者不一致需要显式调用*zoom*.extent对齐。约束函数*zoom*.constrain默认约束函数的实现目标就是确保视口 extent 不超出 translate extent。文档给出了默认实现的完整源码基于transform.invertX/invertY计算越界偏移并回调平移如果你的限制平移语义与默认不符例如允许部分越界可以用*zoom*.constrain替换该函数函数需接收当前 transform、视口 extent 和 translate extent 并返回一个新的 transform。验证限制是否生效文档给出了几个可直接观察到的行为用来确认配置已生效1. 滚轮到达 scaleExtent 边界后会被忽略。当用户已在 scale extent 的某个边界上继续滚动时wheel 事件会被忽略不会发起 zoom 手势。文档解释这样设计是为了让用户放大后能继续向下滚动页面、越过可缩放区域。因此一个直观的检查是反复滚轮放大直到k达到K1此时图形不再变大页面恢复正常滚动。如果你希望滚轮落在图表上时永远阻止页面滚动不管是否到达边界文档给出的做法是额外注册一个 wheel 监听器svg .call(zoom) .on(wheel, event event.preventDefault());2. 用d3.zoomTransform读取当前 transform 检查 k 值。该函数接收一个 DOM node不是 selection返回当前 transform暴露只读属性k缩放系数、x、y平移量。可以在浏览器控制台执行const t d3.zoomTransform(document.querySelector(svg)); console.log(t.k, t.x, t.y);交互之后t.k应落在你设置的[K0, K1]之内。文档同时建议不要把transform.k、transform.x、transform.y直接改写需要派生新 transform 时用*transform*.scale、*transform*.translate或 zoom behavior 上的便捷方法。3. 平移不出世界边界。在translateExtent生效时默认约束函数会尝试保证视口 extent 不超出 translate extent因此拖拽到边界后会顶住不再继续移动。另外注意文档提到的一点translate extent 在缩小时也可能引起位移缩放缩小时 transform 会被平移以保持视口在世界范围内这是预期行为而不是 bug。程序化修改哪些调用执行限制、哪些绕过除了用户交互zoom transform 也可以程序化修改文档对这两条路径的限制行为区分得很明确绕过限制的显式设置*zoom*.transform要求你完整指定新的 transform且does not enforce the defined scale extent and translate extent不执行已定义的 scale extent 和 translate extent。文档给出的即时重置示例selection.call(zoom.transform, d3.zoomIdentity);平滑重置文档示例中用 750 毫秒过渡selection.transition().duration(750).call(zoom.transform, d3.zoomIdentity);注意如果重置目标本身就在限制范围内identity transform 的k 1这两条示例没有问题但如果你用它设置任意 transform越界值不会被纠正。执行限制的便捷方法*zoom*.scaleBy、*zoom*.scaleTo、*zoom*.translateBy从现有 transform 派生新 transform并强制执行 scale extent 和 translate extent。需要按钮驱动的程序化缩放例如放大/缩小按钮应优先使用这三个方法而不是手算 transform 再调zoom.transform。可选关闭双击缩放。双击/双触会发起一段默认 250 毫秒的缩放过渡可通过*zoom*.duration调整设为不大于零则变为瞬时变化。如果不想让双击触发缩放文档给出的做法是在应用 zoom behavior 后移除 dblclick 监听器svg .call(zoom) .on(dblclick.zoom, null);边界情况小结只配置scaleExtent时平移仍然不受限只配置translateExtent时缩放系数仍然可以到达 0 附近即[0, ∞]默认值需要两者配合才构成缩放与平移范围都受限。*zoom*.transform是唯一不执行这两个限制的入口用它做程序化修改前先确认目标值在限制范围内或改用scaleBy/scaleTo/translateBy。translateExtent 的执行依赖视口 extent 正确SVG 场景下留意 viewBox 与width/height的关系必要时用*zoom*.extent显式指定。若想进一步自定义如何限制而不仅是边界值入口是*zoom*.constrain。各 API 的完整签名、默认值和事件表见 docs/d3-zoom.mdscaleExtent、translateExtent、constrain、transform各节d3-zoom 全部方法一览见 docs/api.md。【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考