【d3.js】canvas下力导向图的缩放平移和拖拽兼容

这个周零散解决了一些的关于d3问题,算是入了门,因而写了博客,顺便也作为周报的内容,所以篇幅会比正常我平时写的周报长很多,详细到代码层面。目前除了这次代码提到的v4,也完成了v6版本对应的代码。

1 从简单做起——html单页面文件开发

目前能找到的很多学习用的demo都是cdn方式引入,直接一个单页面html文件开发,而且出问题也能很方便定位查找,再者v6版本刚出不久,对于诸多api也是有了比较大的改动,讨论度不如有教程的v4,所以为了入门,同时为了避免要下各种各样的d3 npm包,还有考虑各种变量冲突、版本冲突问题,我决定先暂缓V6的学习。

为了更方便测试demo,我对于d3的v4版本采用cdn方式引入。我先选择在Sublime Text3上直接进行一个html文件的单页面测试,一个功能一个功能地学。
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

2 选择canvas 而非svg

直接从d3 canvas进行入手确实不是一件简单的事情,尽管v4支持canvas,但是api的成熟和灵活度远不如svg方便,毕竟每一个svg就是一个独立的存在对象,但是canvas就单纯是个画布,想要对里面的对象操作不得不考虑鼠标的二维坐标。但是canvas相对于SVG的最大优点是可以创建数千个单独的元素,而不会真正影响性能,因为DOM只能看到一个canvas元素。但是,由于它是基于像素的,所以呈现效果不会像SVG那样清晰。在分辨率不高的旧屏幕上,图像可能看起来有点模糊。

Canvas依赖分辨率、不支持事件处理器、具有弱的文本渲染能力,最适合图像密集型,其中的许多对象会被频繁重绘。SVG不依赖分辨率,支持事件处理器,最适合带有大型渲染区域的应用程序(比如谷歌地图),但是复杂度高会减慢渲染速度(任何过度使用 DOM )

所以进行大数据可视化的时候,用成千上万的svg会造成dom太多的压力,而canvas在这方面是没有那么大的压力的。

3 d3.json()跨域问题

对于前端老生常谈的问题了,因为脱离了webpack+vue环境下的开发,所以我要重新面临跨域问题,不过这已经不算什么难事了,本质上这个问题还是对d3.json()这个api的不熟悉。根据文档介绍:d3.json()只能从服务器*问json文件,因此不能直接访问本地json文件。因此,我们使用node.js构建一个本地server。

npm安装: npm install http-server -g
启动服务器:http-server 【你的文件夹路径】

【d3.js】canvas下力导向图的缩放平移和拖拽兼容

最好确保html文件和你的json文件是在同一文件夹里。

4 力导向布局模拟

我第一次确切了解这个概念还是在当初做论文翻译的时候,以及之后的调研报告也了解了这个概念,但是目前关于力导向图的模拟d3 api的一些更为具体的功能和细节(例如布局算法的源码)个人并没有深入了解,之后如果遇到实际开发问题再详细观察。而本次重点也并不是解释这个力导向图的存在。

力导布局图是一种用来呈现复杂关系网络的图表。在力导布局图中,系统中的每个节点都可以看成是一个放电粒子,粒子间存在某种斥力。同时,这些粒子间被它们之间的“边”所牵连,从而产生引力。系统中的粒子在斥力和引力的作用下,从随机无序的初态不断发生位移,逐渐趋于平衡有序的终态。这就是对力导布局图的直白解释。力导向图通常在二维或三维空间里配置节点,节点之间用线连接。各连线的长度几乎相等,且尽可能不相交。节点和连线都被施加了力的作用,力是根据节点和连线的相对位置计算的。根据力的作用,来计算节点和连线的运动轨迹,并不断降低它们的能量,最终达到一种能量很低的安定状态。事实上,许多关系网络都是通过力导向图进行可视化的。力引导图可以完成很好的聚类,方便用户看出点之间的亲疏关系。

在我们d3仿真模拟系统中存在多个节点和多种类型的力,通过力控制节点的运动,每个节点都在多个力的作用下不断发生移动,直到系统趋于平衡。中间会发生多次tick事件,每次tick,仿真系统都会更新节点的位置,且系统的能量(alpha)也会逐渐降低,直到达到某个数值(alphaMin),整个图表就停止运动了。

·节点(node)是一个对象数组,对象的属性没有限制,你可以添加多种信息来控制图表的渲染(例如颜色大小等),每次tick都会更新节点的位置(x,y)和速度velocity(vx,vy)。

·力(force)驱动着整个系统运动,你可以给系统添加力,控制节点的运动。例如:

d3.forceCenter([x,
y]):中心力,将所有的节点都推向图表的中心(给定的一个点),默认坐标是[0,0],施加力时,所有节点的相对位置保持不变
d3.forceCollide([radius]):collision,碰撞力,使两个节点接触时像弹簧球一样弹开
d3.forceLink([links]): 连接力,拉动节点相互连接,好像节点之间有一个弹簧
d3.forceManyBody():排斥力,类似带电电子的排斥方式,推动所有节点彼此远离
d3.forceX,d3.forceY:定位力,将节点推向期望的点(x,y),不同于forceCenter,它们会改变节点的相对位置

在这里可以查看不同因素参数下的作用:
https://bl.ocks.org/steveharoz/8c3e2524079a8c440df60c1ab72b5d03
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

个人目前开发重点不在这里所以暂时给予的参数不多,下周继续研究其他设置参数。
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

每次拖动开始,设置alphaTarget并重启仿真系统,alpha的值会从alphaTarget递减到alphaMin,所以如果你将alphaTarget的值设置的比alphaMin小,就会卡住,不会继续更新。其中可以设置alphaDecay衰减系数决定其更新速度(值越大越快)。

给整个系统设置完系统参数之后,接下来就是加料环节——接受json数据并将其转化
例如接受每个节点必须是一个对象。通过模拟分配以下属性:

·index-节点的从零开始的索引节点
·x-节点的当前x-位置
·y-节点的当前y-位置
·vx-节点当前的x-速度
·vy-节点的当前y-速度
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

这个tick事件会随着时间和你手动拖动不断触发更新,可以理解为更新当前系统状态和图像的操作。所以一般思路是讲对系统的变化放在tick事件里面。系统稳定后不会触发。
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

系统当然不会自动帮你生成图像,每次tick或者是其他变化的时候还是需要用到canvas的api不断清除画布并重新生成点和边,听起来很麻烦但确实是这样的。
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

记一下,下面会提到transform

·transform.x - 在x-轴上的平移量 tx
·transform.y - 在y-轴上的平移量 ty
·transform.k - 缩放因子 k

5 拖动的是画布还是点呢?

Canvas监听拖动不像svg那样方便,所以我们还要考虑拖动的是画布还是点,因而有这么一个api
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

用于告知拖动对象是什么
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

6 拖动问题 Drag 和 缩放平移 Zoom的比例失衡问题

遇到一个有意思的问题,初始化图像的时候我的指针拖动点,很明显是正常的——点跟着鼠标动,但是经过缩放平移之后,我再次点击点并尝试拖动的话,点就不受我的控制。经过多次尝试我才理解到是我虽然看着是已经被放缩平移后的图了,但是我鼠标的操作确是和没有平移缩放的图映射的,也就是说我看到的是放大后的图,但是实际上各个点的位置依然还是原来那样,那就奇怪了,也就是我的缩放平移操作没有改变我的鼠标点击坐标事件应该触发的确切位置。

通过观察官方文档

①d3.drag 的文档
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

①d3.zoom 的文档
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

所以我们开始设置初始化一个全局变量
【d3.js】canvas下力导向图的缩放平移和拖拽兼容
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

每次zoom(缩放平移操作)之后:
【d3.js】canvas下力导向图的缩放平移和拖拽兼容

将当前event的transform对象赋值给transform,因为当 zoom event listener 被调用时,d3.event 被设置为当前缩放事件。然后拖动的时候指定拖动主题的时候也要考虑比例映射的坐标问题,每次拖动都要赋予当前比例下的坐标转换值,保证可视和操作层的视图是一致的。

【d3.js】canvas下力导向图的缩放平移和拖拽兼容

不过在这里也可以看到,在canvas里面选择对象是基于鼠标指针位置决定的,不是单纯的点击选择。我们所能做的就是将鼠标侦听器附加到canvas元素本身,获取指针的x和y坐标。这就要求我们在内存中维护一些数据结构,该结构与渲染元素及其原始数据关联的位置相对应。

上一篇:vue整合d3.v5.js制作折线图


下一篇:C# double类型变量比较分析