背景
作为一个一本正经的大数据公司,Helium亟需一个平台HAN(Helium Analytical Network),它涵盖数据完整的生命周期(产生 – 处理 – 使用 – 销毁)。用户通过低学习成本的方式(如SQL, JSON)来定义这个生命周期,即数据从哪来,怎么计算,怎么使用,何时销毁。在平台上,希望用户能用JSON的方式部分覆盖OLAP的功能,即用户从数据的建立到完成OLAP查询的完整过程。
本文的工作主要集中在第一二个阶段,即数据的产生和部分的处理上。相比于传统的前端配置方式,我们的创新点在于将数据集的流向与产生以图形化的方式进行展现,使得用户清晰地看到每一个数据集产生的过程和流向。带来的好处除了形象化、直观之外,还可降低用户在操作数据过程中产生错误的几率,并且及时发现并补救产生的错误。
总览
界面总览
总览
整体界面由三部分组成,比较简洁清爽。最上面是选择Task的panel,因为数据集的操作是分数据集进行的;中间部分是一块画布,展现的是数据集的细节信息;最下方是operators panel,又可细分为左边的数据集与操作panel与右边的系统级操作panel。
文件结构总览
经过数次重构,整体文件的结构如下图:
还是一个比较传统的前端工程,遵循着MVC模式。其中css文件夹控制着整体工程的样式;image文件主要是按钮和数据集相关的一些图片资源,js文件夹下面分为外部引入的资源(library文件夹)和我编写的js文件;jsonData是前后端未联通时,进行测试用的json文件样例,HAN.html是整个系统的入口。
MVC架构图
上面的架构图中,我故意将Controller这部分绘制成了红色,体积也明显大于其他部分,不难看出Controller在本项目中的重要性。跟ios类似,传统的Model仅仅是一些class或者struct(在js中为object),由于本项目中Controller部分太过臃肿,负担过重,我将Model相关的操作也移到了Model模块中,以增加代码的可读性,因此在本项目中,Model理解为Model-related-Module更为合适。
Model部分
Model部分的数据结构本身比较简单,可分为两大类,Stream数据集与Batch数据集;其中数据集本身又可以根据数据来源分为From Souce和From operator两种类型。
Model.js中定义了这两种数据集,由数据集中的一个字段区分数据集本身的来源。对数据集的增删查改功能也放到了Model.js中,上文有所提及,这部分代码本应该放在Controller中的。
Operators在本项目中也是等同与Model的一等公民,因此我们也特意为其定制了相应的数据结构,同样可分为单目操作和双目操作,对每种数据集都设置了相应的字段存储相应的内容。同样放到Model中的,还有对数据集进行操作的逻辑,这样可以大大减轻Controller部分的代码,方便其他同事理解阅读这个项目的源码。下面的关系图很好的说明了数据集和操作符之间的关系,单目操作不改变数据集类型,双目操作只要涉及到Stream类型的数据集,生成的数据集就为Stream类型。
为了方便Controller与服务器端的通信,在这里分别设立了batch数据集、stream数据集、operators数据集的数组,并提供了一套根据数据集/操作的id快速找到相应model的方法,以便于快速将view和model关联起来。
View部分
View部分是这个系统的创新点所在,传统的前端项目使用的是DOM元素,DOM元素没有SVG元素自由,打个不恰当的比方,如果对DOM元素的操作是美图秀秀,那么对SVG元素的操作就是Photoshop。SVG元素的绘制、动画方面的可自定义化非常强。
经过公司领导和其他同事的反复重构之后,我将绘制通用流程图的通用部分抽取了出来,并提供给使用这个库的用户足够的自由,自定义所涉及到的大部分外观样式和监听事件的补充。
构成流程图的基本元素
如上图所示,构成一个完成流程图/数据流图所需要的最基本的两个元素是Entity与Entity之间的关联关系。其中Entity是由两部分组成,即上方的图片(img)和下方的描述文字(text),关于这个部分,我们提供的可视化库(Helium_vis.js)中都进行了深度的定制化,您可以自定义img和text的宽高、img左上角的位置、img的透明度、text的字体、大小、对其方式等一系列您想得到与想不到的属性。
Entity与Entity之间的关联关系目前是定好了从第一个出Entity的图片的右边中点与第二个入Entity左边中点,其他部分由三次贝塞尔曲线进行插值生成漂亮的曲线。关系的render部分目前只提供了粗细的可配项目,后期需要加上关系的描述文字,并提供居中或相对中点偏移量的可配项。
事件部分,我们希望Entity可以被选中,并且将选中的元素进行高亮区别显示,并且在本项目中,选中的Entity所代表的dataset应该在其他地方进行展示,并且在该处可由用户对dataset的model进行修改、删除等操作。而这部分业务逻辑相关的代码显然不应该放到库内,因为其他项目并不需要这个部分的逻辑,很可能需要改成其他项目的逻辑。同样的,作为一个一本正经的流程图库,我们希望用户有权力去修改每个Entity默认的位置,因此也对每个Entity配备了Drag事件,通用Drag事件,Helium_vis实现了Entity本身跟随鼠标的移动和该Entity相关操作(曲线)的实时移动,另外,针对特定的项目,还支持以传入回调函数的方式将项目特定的逻辑进行传入。比如,在HAN这个项目中,由于需要保存并恢复Entity的位置信息,因此在Drag事件中,添加了对Entity位置的计算相关的逻辑。
具体的做法比较tricky,是将每个Entity此次render的初始位置加上此次操作的偏移量保存下来,并以此作为下次打开时的初始render位置。
另外一个可重用并在本项目中反复利用的项目是圆角矩形,在此,我们同样对其进行了封装,提供了很多可配置的选项,并且支持以类似jQuery的链式操作。这样就可以通过配置的方式,用两行代码render出所需要的图形,大大缩减了代码的长度。代码少了,带来的最大好处是bug也少了。
|
|
在底层的Operators panel部分,我们还加入了一个脑动大开的类似新版MacBook Pro上Touch Bar的图标收起、图标展开的动画以展示影藏的二级菜单,得益于SVG元素强大的动画能力,这些操作实现起来并不是很复杂。
Touch-bar like animation
Controller
在本项目中,Controller捕获到view层面的按钮点击事件、drag事件,去调用Model里的增删改查方法;或者经过Model中的from Operator事件产生了新的Model,经过Controller去调用View部分,render出新的视觉元素与之对应。经过前面的功能疏散,这部分代码已经轻量级了很多。
剩下的一大块功能是与服务器的通信,有正向与逆向两个方向。正向的是去主动通知服务器,将某个任务启动起来、将某个任务删除、保存当前任务信息等等。逆向的部分是从服务器端将上次某个task的保存数据获取到,并由Controller调用相关方法进行重新render,并且render后的canvas依然允许用户像第一次进行操作那般顺滑。
点击数据集、Operators后的弹出框、配置框等还是沿用的传统前端技术,并不符合本文主题(用可视交互的方法),因此这里就不展开介绍了。
总结
之前写代码比较混乱,可能是因为我做过太多外包中的毒,只求迅速将功能实现交付给甲方就完事了,很少有对项目整体重构的思考。经过两次公司领导的亲自指导重构,有所反思,重构确实让代码看起来清爽了很多、并且可维护性有所提升,以后写项目的时候,应该提前规划好,而不是立即就上手去做,毕竟磨刀不误砍柴工。
今天是2017年的最后一天了,就将本文作为今年的收官之作吧。最后,祝大家在新的一年里,开开心心、平平安安,每天都能有所进步!
原文作者: Chih-Hao
原文链接: http://zhihaozhang.github.io/2017/12/31/HAN/
发表日期: December 31st 2017, 4:00:53 pm
版权声明: 本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可
-
Next PostAVHider for macOS开发笔记
-
Previous Post谷歌开发者大会(GDD2017)见闻 Day2