时间:2018-10-3 作者:剧中人
在历经两年多的迭代后,Odeon大数据平台在底层能力建设上已经相当丰满。最近大半年来在数据可视化方面也投入了很大的精力,目前自助 BI 产品也已经成功上线并稳定运行了一段时间,今天小剧就来分享下我们在数据可视化中的一些经验。
今天的分享主要围绕 可视化布局模块 的一些工作,布局模块是承载可视化图表的一块画布,是用户用来组织业务逻辑的重要手段之一。先来体验下 在线demo 或者看下面的效果图:
图一:可视化布局模块效果图
其实不管是在数据可视化,还是在一些自助发布的运营平台,自助布局一直是一个难以跳过的问题。在项目开始之初这个问题也同样在困扰着我们,是我们开发几套预置模版让用户挑选,还是开发一套让用户可拖拽配置的布局模块?
前者实现上非常简单,也很容易设计的非常精美,但是缺少了很多灵活性,很容易千篇一律。后者用户在使用上自由度非常高,可以拖拽出更符合自己预期的界面,但是开发成本相对较高,并且想要达到稳定、易用、高辅助性却比较难。
综合考虑上面的这些问题,我们对制定的几套方案均不满意,最终还是决定自己动手实现一个可以辅助用户进行拖拽布局的组件。
经过设计,我们对交互模型做了梳理,设计了卡片、图表两层结构。在此基础上对数据模型做了整体设计,结合上面效果图和 在线 demo 可以更加容易理解下面的划分。
首先是局部范围内单例的 Map 类,用来描述整个 dashboard 布局,以及其他数据存储。
其次是 Widget 类,用来定义卡片信息,包含卡片的尺寸、位置信息,以及更详细的卡片标题、卡片类型、脚注、图表ID 等信息。
最后是 Chart 类,用来定义图表信息。为了布局与图表模型分离,这里的图表信息放置较少,仅仅存储了必要的 id 等字段,图表本身实现由外部 slot 插槽来完成。
图二:数据模型
数据模型构建好了,接下来就是着手设计拖拽交互了。
出于响应式考虑,同时为了减少用户操作的随意性,在布局设计上我们放弃了像素单位,而是针对节点尺寸设计了虚拟的 12 栅格切分,在初始化、缩放、移动时都严格参照此栅格布局。
为了保障用户拖拽体验的顺畅,使布局保持整齐,我们在遵循卡片自动调整逻辑的基础上,设计了卡片的重绘流程,这一点在后面会有详细的介绍。
为了提供更好的拖拽体验,我们额外设计了虚拟卡片用来辅助用户进行布局,用户随时可以预览松开鼠标后卡片处在的位置及大小,效果如下图阴影处所示。
图三:拖拽预览效果图
有上面的数据模型设计,其实一大半的困难已经被解决了。难点主要体现在辅助布局过程中易用性上的一些处理,总结下来有以下几点。
其实单纯的碰撞检测是很容易解决的,毕竟所有卡片只是二维矩形而已,这一点并不是难事,困难的是碰撞后被接触的卡片需要腾出位置进行让位,我们需要找到所有被碰撞到的卡片,以及受此卡片影响的所有后续卡片,这是一个较为困难的点。
图四:碰撞检测布局重绘过程
在设计布局的过程中,用户可能会把卡片拖得七零八落,而且在上面提到的碰撞检测的处理过程中,也会导致相邻卡片受影响。初期的交互在体验后发现,拖拽过程中用户需要花很长时间来重新整理布局,因此为了保证布局排列紧密,必须要有一个方法,能够将所有处于悬空状态的卡片依序吸顶处理。
图五:自动吸顶布局重绘过程
上面提到的这两个困难点,在移动、缩放这两个拖拽交互中都存在,并且逻辑上是一致的,因此在拖拽布局过程中我们抽象出了一套重绘逻辑,按顺序整理如下:
因为拖拽需要实时预览效果,上面这四步重绘逻辑会被频繁触发。为了避免在布局过程中反复污染真实的位置数据,我们为卡片位置我们引入了 previewOffset
字段,用来存放预览效果时的垂直偏移量。
可能会有同学会产生一个疑问:这里是业务上的重绘逻辑,如此高密度的修改数据会不会在性能上表现比较差 ?
其实这个问题的解决得益于 Widget 类 的数据模型设计,在从碰撞检测到自动吸顶等一系列处理过程中,被频繁计算、修改的仅仅为相对单位的 position
对象,而界面布局使用的是绝对单位的 screenPosition
对象。数据与表现是分离的,仅当一个重绘流程结束之后才去触发 screenPosition
的更新,如此设计使得界面更新节奏更可控,界面更稳定,避免了视觉上卡片位置的闪烁的问题。
按照此重绘逻辑,既避免了 dom 层冗余的重绘开销,提高了整体性能,又在界面布局的易用性上得到大大提升,操作体验上也更加稳定、流畅。
本文主要从可视化布局模块的交互逻辑的设计上展开,并未涉及到具体代码实现,作为 Odeon 大数据平台在可视化布局模块的一些思路,相信通过本文您已经有了初步的了解。
如果您对文中提到的碰撞检测、让位处理、吸顶效果以及辅助布局中等具体实现细节感兴趣,欢迎私下进行探讨,可能的话我会另开一篇文章详细展开来聊。