双十一大屏数据可视化技术分享

@xue  November 16, 2017

每年天猫双十一购物节,都会有一块巨大的实时作战大屏,展现当前的销售情况。DataV 数据可视化小组正是这块大屏的创作团队,以下以2015年的数据大屏为例子,和大家分享一下背后的技术细节和点滴。当然也非常欢迎读者交流。
  双十一大屏简介
  2015年的媒体大屏幕分为三大板块,分别是总览大屏,全球大屏,城市大屏。每一个大屏幕都有自己的主题,总览大屏保留了2014年基本的信息点,全球屏是对于今年全球狂欢这一主题的数据展示,而城市大屏是今年在信息点上的一大创新,我们希望通过从地理维度的下探,让来访者对于阿里双十一数据有更多个性化的认识和解读,也让数据和大家更加有贴近感。
  而在展现形式上,本次数据大屏最受关注的两个形式是:3D实时交易地球和3D北京市交易图。这两种形式都将数据从抽象世界还原到了人们所熟悉的3D世界,并且直接转换回了产生这些数据的行为。例如北京3D城市中的一条条飞线正想一件件快递包裹向满心期待的买家们飞奔而去的景象。而看到3D地球的时候,即使是事先不太了解今年全球狂欢主题的来访者,也留下了今年双十一已经影响全球的感受。

1.jpeg
  总览大屏
 2.jpeg
  全球大屏
 3.jpeg
  城市大屏
  在技术上,所有的媒体大屏都是基于web端技术实现的,这样的选择是因为数据大屏幕只是数据可视化在我们日常工作中的一个运用场景,我们需要能让数据可视化的技术能在日常工作中得到最广泛的落地运用,web端当然就是我们的主战场。当然。抛开这一点,我们认为web端的相关技术也是大家落地相关数据可视化开发的良好平台,为什么这么说呢?这基于以下几点:功能全,前端技术svg,canvas,webgl三者的相互补充,已经让我们在交互,2D&3D以及大规模数据量绘制有了充足的发挥空间,其次我们团队在以前开发数据产品的过程中已经用Nodejs建立了良好的中间件来连接各种数据源,这让目前接入各种数据库变得非常便捷。最后就是便于分享,在任何硬件设备上只要打开浏览器就能看到最终的数据可视化作品,在当今这个注重信息分享的时代,这一点非常关键。
  另外这也是第一次我们在公司大型项目上使用DataV数据可视化引擎搭建数据大屏。DataV数据可视化引擎是以我们日常图表组件库d.chart以及地理相关组件库d.map为基础,专业数据可视化模板设计为视觉框架,依托cube前端框架搭建起来的web服务。通过该引擎可以利用已有组件低成本还原设计师产出的原型图,快速完成多种数据源的绑定,并通过最终的可视化配置调整系统完成所见即所得的视觉调试,最终产出数据大屏。
  4.jpeg
  DataV数据可视化引擎大屏管理界面
  项目技术目标
  之前我们做了几年的展示类可视化项目,有些坑有些不足。十月走进项目室的时候,我们有这些期许:
  1、高性能,告别内存泄露 内存泄露一直是我们挥之不去的心病,想象几百个镜头对着马总讲话的时候,运行了10个小时的大屏忽然崩溃是何情景。 过去的几年里,在展示性很强的场合,我们常用svg去表现复杂的图形,还记得去年为了snap.js里存在的内存泄露问题而彻夜不眠(库本身有坑,除此还会有性能问题),而且svg和普通canvas,性能有限,不能使我们能表达万级数量实时更新的图形,我们希望以更高性能的方式,去还原数据的海量。所以,今年大部分的屏幕都是基于webgl的,我们又尝试了许多方法,使得性能更好,且看下文。
  2、走向城市级别 全球和全境的展示,如中国的物流网络,全球的飞线图,已在历年的大屏考验里愈发成熟。 更有意思的方向在哪里?我们觉得走向城市尺度有挑战,如何表现城市错综复杂的建筑和街道结构、以及人类交易活动混沌而有序的空间分布。
  3、从飞线到轨迹 飞来飞去图,展示了很多年,我们如何更进一步,更有意思地展现数据。 我们想到的一个点,是让交易沿着物流的方向行走,营造一个更真实的交易场景。
  4、全面3d 2014年双十一媒体大会里,3d的可视化场景小试牛刀,今年覆盖更广。 3d之于2d,有GPU加速性能上的好处,也为可视化增加了一个空间的维度,还让某些场景更接近感官(如城市建筑的展现)
  5、工程化 第一次高规格的首都双十一晚会,鞭策我们推陈出新,也把大屏开发的数量,提到了去年的4-5倍,时间非常短,调整也很多,如何标准化地去做形态各异的不同大屏?
  技术要点
  双十一盛况空前,海量的订单和支付数据,经由客户端和大后端的处理、公共数据平台及产品部同学的合并和清洗,到了可视化大屏前,面对数百媒体以及媒体背后的观众。首先从我们如何处理和展现这些数据来开始我们的技术分享。
  前端技术
  目前任何一种绘图技术,都很难完整的去反映双十一的海量交易,但我们尽力还原最真实的情况。 数据在数据部门清理之后,进入了我们的流程。
  DataV数据可视化引擎
  总体,我们今年把所有的屏,运行在阿里云的数据产品DataV数据可视化引擎上。DataV数据可视化引擎是一个集屏幕管理、多屏通讯、发布打包等功能为一体的大屏生产工具,除了可以集中管理屏幕,并通过页面遥控大屏,还可以通过配置页面,实时调整已经发布上线的大屏的一些参数。
  前端模型
  我们的架构防止3种情况: 1、防网络延迟/断网 2、内存泄露 3、数据量过大
  我们想表现2点: 1、海量的数据展现 2、飞线群和交易情况一起涨落,比如10日凌晨海量飞线,11日早晨数量变少。
  我们的原则是数据上层层缓存,层层缓冲,组件上利用好GPU通信,架构如下图。
5.jpeg
 几乎所有的屏都以一定地频率请求服务端。接收到数据后还要有处理的策略. 以我们的飞线和轨迹的处理策略为例,讲讲我们处理的流程。
  飞线的数据,接口返回一般都如下格式:
6.png

数据池:
  最简单的动画模型是有啥画啥。 不可能来什么画什么。页面刷新率1/60秒,要维持交易动画的绵绵不断,高频请求服务器,请求很可能无法及时返回造成积累,最后内存爆缸,何况如果下一次数据返回网络不畅,会造成延时,体验很差。
  因此我们的接口以5s左右的速度去请求服务器,拿到数据的时候,也不合适直接画出来。而是在ajax请求和绘制之间,建立一个先进先出,后进后出的数据缓存。数据缓存要具有一定的长度(否则在反复轮询一组飞线,视觉上很明显)。
  每一次数据到达的时候,后端会计算好此刻飞线的繁忙程度(可以理解为0-1的数字),我们通过这个繁忙度,去控制绘制的speed(即浏览器的每一帧刷新,从数据池提取几条飞线数据),这直接决定了此刻飞线的密集度。
  绘制池:
  在绘制层面,维护一个数量固定的飞线对象池,建立回收机制。每个飞线有一定的生命周期,当飞线生命结束(飞到了尽头)的时候,会被回收到unusing的队列里去,当新数据到来的时候,会从unsing的池子里取出飞线复活,放回到using的队列里去。有一种情况是数据量太大,根本没有空闲的飞线时候,新的数据就发过来了,这种情况下,我们拒绝绘制。
  在这个绘制控制器里,我们维护一个requestAnimationFrame的线程,这比每个飞线独自起requestAnimationFrame更新自己的生命周期更节约性能。
  飞线组件:
  今年组件和去年组件的变化,在于数量,组件除了兼容回收机制,最大的改观是所有的组件共享一个线几何体。也就是,几千条飞线其实是一条线,只是我们在视觉上隐藏了线和线之间的连接,在threejs里,建一个100000个点的线,要比 1000条100个点的线要节约大量的cpu资源,而我们把这种虚拟的飞线几何体和一整根线的点,建立了虚拟的映射关系,最后通过shader给线附着颜色。通过颜色和透明度的控制,我们可以绘制出飞线。这种飞线本质是线几何体,形态上没有三角的strip精细,但为了海量,我们选择了平衡(由于错觉,我们觉得有透明度的线比没有的更细,因此有流星拖尾的效果)。
7.png

飞线的延伸
  本质上,飞线就是一个有生命周期的线,全球交易图里,飞线是一根类似于抛物线的曲线,而轨迹图里,飞线沿着经纬度数据的轨迹一步步往前挪动。其实我们可以更推广,任何线产生的动画,我们都可以用这个组件去管理和表达,只是一小部分函数发生变化. 因此,做了双十一前10亿交易额的彩蛋。我们的主要想法是,双十一海量的成交额如雨点般落在了地面上,像焊枪一样焊接出11.11几个大字,每300多万订单完成,就生成一个cube,到10亿元完成的时候,模型旋转并消失。 一方面我们可以看出成交量在不断累加,一方面,在这个时间里,折线图这种更精确的图表,可以有个缓冲时间检查是否有误。
  订单雨
  1、smooth step函数, 这是一个有意思的函数,详见 https://en.wikipedia.org/wiki/Smoothstep我们对 smooth step做一些改进,整体开n次方, 让线条的形态更可控
8.png

我们可以利用这个函数,绘制出空间任意两点的流动关系,这比绘制贝塞尔线要高效率很多,我们用这个函数模拟了订单雨。
9.jpeg

 cube字
  1、sketchup 和模数系统。建筑的模数系统,设计的栅格模式,都是为了产生可控制的比例,让设计更好看更易控制。为了在短时间内建出可控的11.11的模型,我用了sketchup建模,并打了个栅格的布局,并在模型软件rhino中提取出每个cube的中心点。为了完成动画从下而上的变化,我们还需将每个cube的中心点按照z轴排序,但为了让同一层的立方体绘制顺序更有机,我们对同一层的点进行了随机排序。
10.jpeg

2、一笔画问题。 我们用连续的动画生成一个的方块,需要遍历方块上所有的点。 可惜,不能完美地一笔画方块,让每个顶点出现一次,这正是哈密尔顿问题。 我们最后的路径是 0 -> 1 -> 2 -> 6 -> 5 -> 4 -> 7 -> 3 -> 0 -> 4 -> 7 -> 6 -> 5 -> 1 -> 2 -> 3

11.png
效果:
12.jpeg
13.jpeg
其中还有很多细节,如用web worker进行数据处理,尤其是对轨迹数据的前端数据实时简化等。
  3D北京城市 首先,在绘制3D建筑时,Three.js中的ExtrudeGeometry函数提供了非常简便的方法来绘制3D的几何图形。通过绘制城市轮廓并将其挤出到对应建筑数据的高度,来达到建筑的效果。但这只允许绘制大概规模在几百数量的建筑。
14.jpeg
15.jpeg

针对城市规模的绘制,其中包括建筑,道路,及河流,通过合并几何图形的方法,通常将上述所有几何图形合并成唯一的几何图形,来节省CPU与GPU的计算,并减少两者间的通信,从而实现大规模城市的绘制。 同时,根据城市规划的不同,有些城市中存在大量高度为一层的建筑。尽管通过3D绘制方法来绘制,这类建筑的高度仍是微乎其微甚至不可见的,在这种情况下,将此类建筑归为2D建筑,2D建筑的好处是仅通过单一的面就可以展现出建筑的形状,这样可节省大量的计算,同时保证性能的优化。 最后,如果希望展示城市中某一或几个建筑的细节,需要建立obj格式或dae格式的3D模型,通过Three.js将这些3D模型导入原本这些建筑所在地图上的位置。
16.jpeg
 在处理建筑的阴影时,可以通过在城市底部建立新的平面图层,来接收建筑的阴影,同时可以在Three.js中选择让建筑也同时接收阴影,这样,根据灯光的位置,就可以模拟出愈发真实的城市。在有些情况下,旋转灯光的位置,来观测建筑阴影的变换,可以模拟出城市中一天的阴影变化。 数据层面 数据内容为建筑的轮廓数据,在导入前在nodejs里进行后端优化,通过减少距离太近的点(可直接使用 Leaflet 作者在github分享的simplify.js)从而减少对建筑面的绘制。 通常城市的建筑大概在几十万左右的数量,首先根据重点区域进行抽析,减少所绘制的建筑数量,但即使这样城市的轮廓集合数据依然非常大,前端一次性读取如此庞大的数据,等待时间过长,在此解决方式是,需要按照建筑数据id随机的方式切成若干个小的文件,减少每次请求的大小,这样就可以以一种符合视觉的方式载入地图。
17.jpeg

飞线层面上,通常使用飞线的效果来表现城市中的物流。在实时数据中由于物流是不断增加的,所以飞线理所应当的在不断的增加,但是不断创建新的飞线会导致内存泄漏,这就需要对消失的飞线进行复用。由于飞线的数量同样会增加CPU与GPU的通信,而影响性能,所以我们会将所有线段合并成唯一的线段。之后,将这根线段分散成成需要的飞线数量,每一个小的线段进行更新位置,来达到飞线的效果。
  工作模式 (设计-> 开发) =》 (设计-> 开发 -> 设计-> 开发)
  数据可视化,开发不一定能够还原设计师的美学理想。而开发有时能创造设计难用ps和ai表达的图形,如何协作? 这次,我们利用dat-gui等控制组件,根据设计稿,做出了大致的demo界面,让设计师根据拖动条,像操作软件一样进行调整,而我们通过console中打印出来的配置,返到代码里去。 这么做还有个好处,我们现场接入的都是预发和线上的页面,代码不易修改,而大屏和我们电脑的屏幕有很大的色差,所以我们可以通过这种slider,针对大屏的显示,再进行微调,非常方便。
18.jpeg


添加新评论