当前位置: 首页 > news >正文

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

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

文章目录

  • 【趟坑记录】`d3.zoom()`的正确使用姿势 @d3.v7
      • 问题重现
      • 原因分析
      • 解决方案
        • 放缩平移写法
        • 特殊修改`'transform'`函数的写法
      • 总结

在开发一个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})`

,问题就出现了,如下:

bugreproduce

可以看到,在设置了特定的'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()来修改。

完整代码:

//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

【趟坑记录】d3.zoom()的正确使用姿势 d3.v7 文章目录 【趟坑记录】d3.zoom()的正确使用姿势 d3.v7问题重现原因分析解决方案放缩平移写法特殊修改transform函数的写法 总结 在开发一个D3应用的时候遇到了一个 zoom相关的问题&#xff0c;记录解决思路与方案 问题重现 最近在…...

基于 Docker + Nginx + Gitlab-runner 实现前端自动化部署流程

本篇会用到Docker&#xff0c;Gitlab-runner等相关工具&#xff0c;如果对其不是特别了解&#xff0c;可以参考下相关文档&#xff1a; GitLab RunnerDocker 快速入门CI/CD&#xff1a;持续集成/持续部署 在早期部署前端项目时&#xff0c;我们通常会通过ftp把前端代码直接传…...

make/makefile的使用

make/makefile 文章目录 make/makefile初步认识makefile的工作流程依赖关系和依赖方法make的使用 总结 make是一个命令&#xff0c;是一个解释makefile中指令的命令工具&#xff0c;makefile是一个文件&#xff0c;当前目录下的文件&#xff0c;两者搭配使用&#xff0c;完成项…...

Flutter中Navigator 跳转传参数和反向传参数

初始化路由 MaterialApp(routes: <String, WidgetBuilder>{"/Second": (BuildContext context){return Second("");}}, 跳转传参数 String va await Navigator.of(context).push(MaterialPageRoute(builder: (content) {return Second( demo); },…...

kettle开发-Day40-AI分流之case/switch

前言&#xff1a; 前面我们讲到了很多关于数据流的AI方面的介绍&#xff0c;包括自定义组件和算力提升这块的&#xff0c;今天我们来学习一个关于kettle数据分流处理非常重要的组件Switch / Case 。当我们的数据来源于类似日志、csv文件等半结构化数据时&#xff0c;我们需要在…...

MySQL下载与安装

MySQL下载与安装 一、下载 地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 当前最新是8.0版本&#xff0c;我选择上一个最新的mysql-5.7.24-winx64.zip 二、安装 MySQL安装文件分两种 .msi和.zip &#xff0c;.msi需要安装 zip格式是自己解压&#xff0c;解压缩之后…...

c++基础2

文件操作 程序运行时产生的数据属于临时数据&#xff0c;程序一旦运行结束都会被释放 通过文件可以将数据持久化 c中对文件操作需要包含 文件类型分为两种 文本文件&#xff1a;文件以ASCII码形式存储在计算机中二进制文件&#xff1a;文件以文本的二进制存储在计算机中&a…...

虚拟机VMware,linux,centos,如何将项目部署到服务器上面

vmware 是安装虚拟机的软件&#xff0c;centos是系统&#xff0c;linux是系统内核 将本地项目上线到服务器上面&#xff0c;如何实现呢&#xff1f; 准备好服务器&#xff0c;可以选择阿里云服务器 首先需要搭建环境&#xff0c;运行的主要环境是jdktomcatmysql; 通过远程连接…...

R语言 BPNN 反向传播神经网络

##BPNN-neuronet set.seed(123) folds <- createFolds(y=data$Groups,k=10) 建一个放auc值的空向量 auc<-as.numeric() Errorrate<-as.numeric() accuracy<-as.numeric() sensitivity<-as.numeric() specificity<-as.numeric() roc <- vector("li…...

回归预测 | MATLAB实现TCN-BiGRU时间卷积双向门控循环单元多输入单输出回归预测

回归预测 | MATLAB实现TCN-BiGRU时间卷积双向门控循环单元多输入单输出回归预测 目录 回归预测 | MATLAB实现TCN-BiGRU时间卷积双向门控循环单元多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 ![6 基本介绍 1.MATLAB实现TCN-BiGRU时间卷积双向门控循…...

Qt使用QPixmap类和QScreen类来实现简单截图功能

在Qt中&#xff0c;可以使用QPixmap类和QScreen类来实现截图功能。 以下是一个简单的示例代码&#xff0c;演示了如何在Qt中进行截图&#xff1a; #include <QtWidgets>void captureScreen() {// 获取屏幕对象QScreen *screen QGuiApplication::primaryScreen();// 截…...

【【51单片机LCD1602模块介绍】】

LCD1602的介绍 显示容量16x2 每个字符是5x7的点阵 VDD 是电源正极 4.5-5.5v VO 是对比度调节电压 RS 数据/指令 选择 1为数据0为指令 RW 读写选择1是读 0为写 E 使能 1为数据有效 下降沿执行命令 D0-D7 数据输入输出 A 背光电源正极 K 背光电源负极 LCD1602的操作流程 1.初始…...

【Nginx11】Nginx学习:HTTP核心模块(八)文件处理

Nginx学习&#xff1a;HTTP核心模块&#xff08;八&#xff09;文件处理 继续我们的 HTTP 核心模块之旅。今天主要是文件相关的一些处理操作&#xff0c;包括 DirectIO、文件缓存以及 sendfile 相关的配置。这三个配置中&#xff0c;大家应该会见过 sendfile &#xff0c;但是另…...

STM32MP157驱动开发——按键驱动(休眠与唤醒)

文章目录 “休眠-唤醒”机制&#xff1a;APP执行过程内核函数休眠函数唤醒函数 休眠与唤醒方式的按键驱动程序(stm32mp157)驱动程序框架button_test.cgpio_key_drv.cMakefile修改设备树文件编译测试 “休眠-唤醒”机制&#xff1a; 当应用程序必须等待某个事件发生&#xff0c…...

全面解析 SOCKS5 代理与 HTTP 代理的对比与应用

一、 SOCKS5 代理与 HTTP 代理的基本原理 SOCKS5 代理&#xff1a;SOCKS5 是一种网络协议&#xff0c;它可以在传输层&#xff08;Transport Layer&#xff09;代理 TCP 和 UDP 请求。SOCKS5 代理不解析请求内容&#xff0c;而是直接将数据中转至目标服务器&#xff0c;支持更广…...

STM32 HEX文件和BIN文件格式区别keil中的配置与生成

一、区别 HEX 文件: 是包括地址信息的,在烧写或下载HEX文件的时候,一般都不需要用户指定地址,因为HEX文件内部的信息已经包括了地址。HEX文件是用ASCII来表示二进制的数值。例如一般8-BIT的二进制数值0x3F,用ASCII来表示就需要分别表示字符3和字符F,每个字符需要一个BYTE…...

RabbitMQ优先级队列的使用

RabbitMQ优先级队列的使用 生产者 public class PriorityQueue {public static void Send(){string path AppDomain.CurrentDomain.BaseDirectory;string tag path.Split(/, \\).Last(s > !string.IsNullOrEmpty(s));Console.WriteLine($"这里是 {tag} 启动了。。&…...

MAC 推送证书不受信任

配置推送证书的时候&#xff0c;一打开就变成不受信任&#xff0c;搜了很多解决版本。 由于苹果修改相关规定&#xff0c;推送证书 打开Apple PKI - Apple 下载AppleWWDRCA文件&#xff0c;选择G4,双击安装之后&#xff0c;证书已经变为受信任。 AppleWWDRCA(Apple Worldwid…...

Gitee创建分支

在使用Gitee进行代码托管时&#xff0c;分支是一个非常重要的概念。它可以让我们在不同的开发阶段、不同的团队成员之间协作开发&#xff0c;提高团队工作效率。因此&#xff0c;下面将介绍如何在Gitee仓库中建立分支。 一、在Gitee上创建新的分支 在讲解如何在Gitee上创建新…...

集群间ssh配置免密登录

ssh免密配置&#xff0c;可以将ssh生成的密钥分发给目标主机&#xff0c;之后再用ssh访问目标主机时就无需输入密码 下面我们来配置用centos71免密登录centos72主机 使用下面指令生成一个密钥 ssh-keygen其中会提示&#xff0c;是否输入密码短语&#xff0c;这里不输入&#…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...