【趟坑记录】d3.zoom()的正确使用姿势 @d3.v7

这篇具有很好参考价值的文章主要介绍了【趟坑记录】d3.zoom()的正确使用姿势 @d3.v7。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【趟坑记录】d3.zoom()的正确使用姿势 @d3.v7


在开发一个D3应用的时候遇到了一个 zoom相关的问题,记录解决思路与方案

问题重现

最近在开发一个D3应用的时候遇到了一个zoom相关的问题,应用里有一个功能叫全景聚焦。我们都知道画布由两个标签组成(见实现autoZoom(),画布自适应放缩并居中@D3.js-v5),最外层的是固定视口<svg>,一般将zoom事件绑定在<svg>上;内层是具体的画布,是一个<g>标签,在<svg>中的放缩与平移操作都作用在<g>上,修改<g>transform属性。这么做是为了避免用户将<svg>元素拖动到窗口之外后丢失拖动焦点,无法将其拖回。而如果使<svg>不动,<g>被拖动,那么拖动焦点就不会丢失,用户将<g>元素移动至视口外后,还能将其拖回来。

我之前习惯这么写拖动平移:

const svg = d3.select('#viewport')
        .attr('width', width)
        .attr('height', height)
const g = svg.append('g')
        .attr('id', 'container')
        .attr('width', width)
        .attr('height', height)

svg.call(
  d3.zoom().on('zoom', (e) => {
    const transform = `translate(${e.transform.x},${e.transform.y}) scale(${e.transform.k})`
    g.attr('transform', transform)
  })
)

在一些业务场景中,往往需要对<g>元素进行特定的平移与放缩。如:自动缩放至视口中央,放大至当前的1.5倍。然而,在其他直接地方修改了<g>‘transform’属性后,如:

const offsetX = 10;
const offsetY = 10;
g.attr('transform',`translate(${offsetX},${offsetY})`

,问题就出现了,如下:

【趟坑记录】d3.zoom()的正确使用姿势 @d3.v7,前端学习,数据可视化,js,数据可视化,visualization,d3.js,D3,zoom,d3.zoom

可以看到,在设置了特定的'transform'后,再进行拖动,会出现瞬移。

原因分析

因为监听的zoom事件是通过e.transform来进行放缩的。而在修改<g>元素的‘transform’属性为一个特定值后,再进行拖动,会从上一次的e.tranform值开始修改,因此会出现错误。

举例说明:

  1. 用户拖动,e.transform的数值修改为了transform_1
  2. 有一个自动放缩函数autoZoom,将<g>'transform'修改为了transform_2
  3. 用户再次进行拖动,<g>'transform'会从transform_1开始修改,因此会出现从transform_2transform_1的瞬移。

解决方案

得知原因之后,解决方案也非常明了。就是在任何需要进行放缩平移的地方,都将transform进行缓存,下一次再需要进行放缩平移操作时,从上一次的transform开始进行更改即可。

一开始我想的解决方案是在每次鼠标拖动时都记录一个偏移量,但是这个偏移量比较难获取,心想d3这么大个库应该不至于用这么蠢的办法,应该有更好用的方案。

查了一下官方的API,发现了一个叫zoomTransform(node)的接口,这个接口传入的是一个HTML node,需要用d3.select(xx).node()来获得,可以获取这个node的放缩数据。官方文档是这么说的:

Internally, an element’s transform is stored as element.__zoom; however, you should use this method rather than accessing it directly. If the given node has no defined transform, returns the transform of the closest ancestor, or if none exists, the identity transformation


在内部,元素的变换存储为 element.__zoom;但是,您应该使用此方法(指的是zoomTransform)而不是直接访问它。如果给定节点没有定义的变换,则返回最近祖先的变换,或者如果不存在,则返回恒等变换。返回的变换表示以下形式的二维变换矩阵(略):

These properties should be considered read-only; instead of mutating a transform, use transform.scale and transform.translate to derive a new transform.


这些属性应被视为只读;使用transform.scale和transform.translate来派生新的变换,而不是改变变换。(下文将介绍如何派生新的变换)

进一步查看了源码,发现在svg.call(zoom)这个操作后,<svg>这个HTML node就会绑上一个__zoom 属性,这个__zoom属性记录的是transform参数,也就是我们对<svg>进行的放缩平移变换。为此我还特定打印了一下,发现确实如此:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YmbofakK-1689904577005)(/Users/zqqcee/Library/Application Support/typora-user-images/image-20230720100136154.png)]

那现在事情就变得很简单了,可以转变一下思路。之前我一直希望能够在autoZoom()之后,获得"zoom"事件的偏移量,使得我能够接着这个'transform'值修改。那么既然我无法获得偏移量,可以尝试在autoZoom()方法中不要直接修改<g>'transform'属性,而去修改<svg>.__zoom值。

放缩平移写法

在一开始时,使用d3.zoom()创建放缩对象zoom,并在任何时刻都使用<svg>call(zoom)修改放缩值。在绑定"zoom"事件时,因为<svg> callzoom,因此任何偏移量都会记录在<svg>,在修改<g>'transform'属性时,可以直接使用d3.zoomTransform(svg.node())来获得<svg>.__zoom来进行应用。

const svg = d3.select('body').append('svg');
const g = svg.append('g');
const zoom = d3.zoom().on('zoom',()=>{
  g.attr('transform', d3.zoomTransform(svg.node()));
})
特殊修改'transform'函数的写法

这里需要说明一下autoZoom()的写法,假设我们现在已经计算出了'transform'数值transformXtransformYk。现在需要修改<svg>__zoom属性为当前的'transform'数值。

查阅了官方文档,找到了可以使用的API:

  • d3.zoomIdentity。这个API可以创建一个新的'transform':{x:0,y:0,k:1},并允许使用transform.translate(x,y), transform.scale(k)对其进行更改。
  • selection.call(zoom.transform,new_transform);使用这个接口能够将<svg>.__zoom修改为new_transform

综上,代码为:

const new_transform = d3.zoomIdentity.translate(transformX, transformY).scale(k);
d3.select('svg').call(zoom.transform,new_transform);

总结

简而言之,任何对<g>的放缩与平移操作,都需要作用在<svg>上,并且使用<svg>.__zoom()来修改。

完整代码:文章来源地址https://www.toymoban.com/news/detail-604355.html

//zoom事件绑定
const svg = d3.select('body').append('svg');
const g = svg.append('g');
const zoom = d3.zoom().on('zoom',()=>{
  g.attr('transform', d3.zoomTransform(svg.node()));
})

//需要修改特定transform的函数,以autoZoom为例
const autoZoom = (transformX,transformY,k) =>{
  const new_transform = d3.zoomIdentity.translate(transformX, transformY).scale(k);
	d3.select('svg').call(zoom.transform,new_transform);
}

到了这里,关于【趟坑记录】d3.zoom()的正确使用姿势 @d3.v7的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • windows自带远程桌面连接的正确使用姿势

    目前远程办公场景日趋广泛,对远程控制的需求也更加多样化,windows系统自带了远程桌面控制,在局域网内可以实现流程的远程桌面访问及控制。互联网使用远程桌面则通常需要使用arp等内网穿透软件,市场上teamviewer、Todesk、向日葵等远程桌面软件十分优秀,window系统也自

    2024年02月10日
    浏览(46)
  • SpringBoot项目中使用缓存Cache的正确姿势!!!

    缓存可以通过将经常访问的数据存储在内存中,减少底层数据源如数据库的压力,从而有效提高系统的性能和稳定性。我想大家的项目中或多或少都有使用过,我们项目也不例外,但是最近在review公司的代码的时候写的很蠢且low, 大致写法如下: 其实Spring Boot 提供了强大的缓

    2023年04月10日
    浏览(31)
  • elasticsearch 父子文档使用must not 正确姿势

    1、基于elasticsearch 父子文档进行子条件查询父文档 2、需要查询出子文档不存在的父文档 1、父文档clue_list 关联很多的子文档,我们用roam子文档做测试! 2、roam子文档的结构 需求是查询出roam子文档不存在的数据,由于我们es的所有数据companyId == 1 我们可以用must not来排除 co

    2024年02月05日
    浏览(45)
  • 逍遥自在学C语言 | 条件控制的正确使用姿势

    在C语言中,有三种条件判断结构:if语句、if-else语句和switch语句。 第一位闪亮登场,有请今后会一直教我们C语言的老师 —— 自在。 第二位上场的是和我们一起学习的小白程序猿 —— 逍遥。 基本语法 代码示例 运行结果 基本语法 代码示例 运行结果 代码示例 在这个示例中

    2024年02月03日
    浏览(54)
  • 提高代码质量!详解在Gradle项目中使用PMD的正确姿势

    当今的软件开发需要使用许多不同的工具和技术来确保代码质量和稳定性。PMD是一个流行的静态代码分析工具,可以帮助开发者在编译代码之前发现潜在的问题。在本文中,我们将讨论如何在Gradle中使用PMD,并介绍一些最佳实践。 PMD是一个用于Java代码的静态代码分析工具。

    2023年04月10日
    浏览(45)
  • Spring Boot 3.2项目中使用缓存Cache的正确姿势!!!

    你是否曾想过为什么在 Spring Boot 应用中缓存是如此重要?答案在于它通过减少数据检索时间来提高性能。在本文中,我们将深入探讨缓存对微服务模式的影响,并探讨根据操作易用性、速度、可用性和可观测性等因素选择正确缓存的重要性。我们还将探讨如何最大程度地提高

    2024年02月05日
    浏览(70)
  • 详解async 与 await,带您理解Playwright使用异步方法的正确姿势!

    大家在使用python做playwright自动化测试的过程中,一定会发现下面这种异步用法 很多同学可能只是按照这种写法来编写项目的自动化测试代码,对于具体细节可能并不了解,今天我就来讲一下playwright异步用法的相关技术细节。建议大家拷贝文档中的脚本实际运行一下,学习的

    2024年02月12日
    浏览(54)
  • 在vue3中使用WebSocket的正确姿势,优雅~实在是太优雅了~

    此教程针对typescript,提供断线自动重连,断线数据重发,自动心跳,自定义消息发送机制 测试用例地址:在vue3+typescript-websocket示例 安装tools-vue3工具库,此仓库提供了http请求、文件请求、websocket、signalr、cookie的功能 创建 WSUtil.ts文件 内容: 发送数据 主动断开 消息处理 对

    2024年02月12日
    浏览(38)
  • React18.x + i18next + antd 国际化正确使用姿势及避坑指南

    如果你使用这个教程还不能够解决你的问题的话,直接私信我,免费一对一给你解决。 具体的创建方法大家参考vite官方文档,大概的操作如下,如果需要更详细的,大家去自行搜索即可 因为我这里使用的是ts版本,所以,你自己看着办吧。 其中 i18next-browser-languagedetector i1

    2024年02月05日
    浏览(62)
  • 如何使用前端绘图库(D3.js、Chart.js等)?

    聚沙成塔·每天进步一点点 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发者,这里都将为你提供一个系统而

    2024年02月08日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包