滴滴: 可视化大屏产品在滴滴的技术探索

现代的数据可视化产品相较于之前的仪表盘应用,在数据方面呈现更加生动、数据实时性高、交互更为友好、效果更加震撼等特点,越来越多的人倾向于通过各类可视化产品使静态的数据“活”起来。基于此背景,我们结合滴滴的各业务线发展,打造了本文介绍的数据可视化大屏产品。

1. 前言

随着技术的发展,更多的人不满足于使用基础的图表来展示数据,如何让数据更直观、更炫酷的展示成为了大家的追求。为了能更好的展示滴滴各业务线的发展,本文介绍的数据可视化大屏从城市、全国和全球维度展示了业务线的实时信息,下面将从基础地图的构建、轨迹、气泡、热力、飞线、散点六个方面来讲述一下开发中遇到的难题及解决方案。

2. 开发流程

前期主要是产品侧对接需求,同时与设计师侧打磨设计稿,泛前端团队的介入主要是在拿到产品设计稿后,将精力聚焦在以下方面:

  • 难点和潜在性问题梳理

    • 地图的一次性加载,考虑到易用性和维护性,需自研一套地图框架;
    • 大量数据涉及到的性能问题,包含数据的计算、传输和实时渲染;
    • 数据业务方较多,接口稳定性和维护性问题;
    • 可视化还原度;
  • 技术攻坚

    • 自研地图框架map3;
    • 将数据计算移到后端,对数据进行压缩,同时在大量数据的传输方面采用ArrayBuffer。尽量减轻GPU和CPU的压力,提高页面流畅度。
    • 因为数据大屏对数据的依赖性较强,为了保障展示的稳定性,对数据采取了缓存兜底方案;
    • 通过开发调试面板,降低与设计师沟通的成本,为提高可视化还原度提供了便利。
  • 打磨验收

通过上文提到的调试面板以及录屏,和设计师开启远程调试阶段。此外在验收过程中还涉及到各个模块的版本迭代,每次验收之后可能都会涉及到相关模块样式及功能的改动,甚至是整个大屏的变动,所以整个不断调整和优化的阶段也是非常耗时的。

该数据可视化大屏采用的是webgl等技术,在浏览器端对渲染的效果进行展示。webgl是一个较为冷门的话题,遇到的很多问题很难直接找到通用的解决方案,更多的是团队人员的一些思考,所以可能并不是最完美的。下面将从基础地图的构建、轨迹、气泡、热力、飞线、散点六个方面来简单介绍一下。

3. 地图构建

以北京为例,我们对整个北京市进行了建模,包括道路、水系、建筑等,此外还对10余个地标性建筑进行了精细处理,例如望京SOHO、CCTV、故宫、工人体育馆、中关村等是以精模形式展现在地图上。总体以较为沉浸式的感官体验进行展示,通过轨迹、订单位置和订单热力等对滴滴的业务进行了实时的可视化展示。

技术选择

要构建图片中的城市,首先想到的肯定是借助强大的开源社区。目前大多数的开源地图框架都支持3d矢量绘制,例如mapbox、cesium等,mapbox的官网中也已经展示了与threejs结合的示例,但是对于北京屏的场景,开源显得还是有些不够用,主要体现在一下几点:

1)效果方面

使用mapbox,瓦片数据会随着视角的移动重新加载,这对于移动应用或者普通的pc是件好事,但是对于大屏场景反而成了问题。我们需要一次性把道路、建筑等地图信息都加载进来,在镜头移动的过程中不能出现重新加载这种十分影响体验感的现象。

2)易用性、可维护性方面

北京屏需要使用大量的webgl,我们选择了threejs这一套库来承接。mapbox官网中展示了与threejs结合的代码示例,但是其中涉及到了大量threejs与mapbox矩阵的转换,所以如果选用mapbox,就需要开发人员和后续维护人员都非常熟悉这一套繁琐的转换规则,可能还需要进一步深挖mapbox底层的渲染逻辑。

3)性能方面

北京屏需要绘制、实时处理的数据量都非常大,加之页面中镜头还需要不同的运动,所以GPU和CPU负载都非常高,项目中我们已经使用了非常高配置的显卡和CPU,但是在开发上,我们仍然需要尽可能的减少性能消耗。如果使用mapbox与threejs结合的方式,如何把性能做到最优是一个很大的问题,因为涉及到两个框架在很多方面的协调问题。

所以综合以上三点的考虑,我们决定在现有技术的基础上,研发一套地图框架map3。这套库在渲染上选择了threejs,API设计上参考了mapbox,非常适合大屏可视化场景。

技术选择

map3的使用方法和mapbox十分相似,传入以下配置项我们就可以渲染出对应的地图:

const map3 = new Map3({
  container: string | HTMLElement;
  style: Style;
  center?: number[];
  pitch?: number;
  bearing?: number;
  zoom?: number;
  controller?: boolean;
  hash?: boolean;
});

其中,style是我们传入的样式数据,针对本项目中的北京地图,style文件中包含了背景、灯光、天空、结构(建筑、道路、水、绿地等数据)、精模(望京SOHO、CCTV、故宫、工人体育馆、中关村等精细模型数据)。通过在map3中对style数据一层层的解析和渲染,最终我们可以得到一次性加载完成的北京地图。

数据压缩

项目中需要用threejs来绘制整个北京城,包括道路、建筑、绿地、山脉等,数据量超过300M,且要求一次性加载完毕。平时js文件超过2M就要考虑优化,300M已经突破了浏览器的最大限制,即这么大的文件浏览器不会缓存(当然可以通过设置来更改这个最大限制,但是首次加载是必须的),所以要考虑如何压缩文件。

mapbox的geobuf可以解决这个问题。geobuf能以近乎无损的方式将geojson压缩6-8倍,即使经过gzip处理也能压缩2-2.5倍。通过源码大致了解其压缩原理,下面写出来与大家交流下,有不完善的地方望指正。

geobuf使用protocol buffers作为数据存储的格式,protocol buffers是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC数据交换格式。它是与语言无关、平台无关、可扩展的序列化结构数据格式。关于protocol buffers的介绍这里不再赘述,大家可以参考官网或网络上的一些介绍,这里只重点指出与geobuf相关的一个特性,就是varint。varint的介绍及原理,也可以参考官网或https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html,这里直接说下其特点。varint的特点就是存储较小的数据所用的字节数很少,例如存储1、2、3这样的数字,就只用1个字节。varint的这个特性与后面所要讲的geobuf的压缩有很大关系,所以这里重点强调下。

geobuf把数据压缩的那么小,还是涉及很多方面的优化,下面我将对个人认为压缩得最厉害的地方做一个介绍。以下是一段geojson,描述了一段LineString,geobuf并不是直接使用浮点数来存储这些数值,而是将数值做了一个计算,除第一组经纬度外,其他经纬度取与前面经纬度的差值,再将差值乘1e5(为了方便介绍,后面这一步暂时称为整型化,具体是乘1e5还是1e6或者其他值,取决于geojson本身的精度)。

以上面的数据为例,取差值后的值为[[120.07045, 30.20248], [0.00001, -0.00022], [-0.00001, -0.00009]...],整型化后的值为[[12007045, 3020248], [1, -22], [-1, -9]...]。geojson描述的无论是polygon还是linestring,coordinates里面的经纬度值其实都比较接近,所以处理后的数值都会比较小,这样再使用varint来存储就会得到比较好的压缩效果。

最后说下项目中对geobuf的改进。主要包含以下两点:

  • 1)因为项目中绘制的区域相对较小,例如杭州,范围在东经118°21′-120°30′,北纬29°11′-30°33′ °,这样对于每一条LineString或者polygon的首个点来说,存储全部的数值也存在一些浪费,于是我们在计算前加入了一个减法计算,将所有的经纬度减去一组数值,例如上面的例子中,减去[120, 30],这样数值将变为[[7045, 20248], [1, -22], [-1, -9]...],首个点的数值更小了,所需要的字节数也更少了(这一步虽然减小了一点数据量,但失去了灵活性,需要权衡利弊)。
  • 2)地图绘制都存在投影计算,我们将投影计算放在了encode的代码中,浏览器接收到数据decode后可以直接使用,这步可以减小浏览器的计算,节省cpu和计算时间的开支。

改良后的geobuf叫做mbuf,下面是一组对比数据:(注:geobuf和mbuf还剔除了一些不需要的属性,所以geobuf相比官方提供的测试数据压缩比更大,官方提供的数据中gzip后可压缩2-2.5倍)

可视化还原

上图为北京屏的设计稿。设计稿是一张二维的图片,需要将此二维图片还原到3d场景,但是二维可以设置的参数与三维完全不一致,例如三维中通常需要设置材质、光线的属性,而这些参数二维中是没有的,因此一开始只能凭经验靠感觉来调整。但是对于开发来说,如何恰到好处的把各个参数调整到位是一件非常头疼的事情,而且很难精准的达到设计师想要的效果。问题不好解决,沟通成本也很高,不如一劳永逸,开发一个工具给设计师调整,于是就有了map3中的configer工具。通过可视化的调试面板,让设计师通过调整参数来还原场景效果,在附加上一键导出所有配置参数的功能,保留配置并直接应用在map3中,这一切就变得简单了许多。

工具还附带可水纹效果、背景效果,可以把three.js官网中的示例很方便的接入进来,还可以使用threejs中的各种材质,添加多种类型的灯光。

4. 轨迹

这部分主要介绍的是上图长轨迹的实时绘制,同时这也是影响性能的很重要的一部分。正式展开叙述之前先看下需求是怎样的。如动图中所示,需要获取实时轨迹数据在前端进行展示,轨迹需要流动起来,且在地图视野拉近(近看城市)时运动变慢、轨迹变细,在地图视野拉远时(俯视北京全城)运动变快、轨迹变粗。效果要反映真实的订单情况,所以数据需要实时更新。下面从几个重要的点来展开介绍。

绘制轨迹

受Chrome的限制,webgl绘制线条的时候只能绘制1px。因此我们只能通过绘制面的方式来绘制线。关于绘制方式,网上有大量的文章,这里不做赘述(eg:https://zhuanlan.zhihu.com/p/35837423)。画好线条之后再将纹理映射到线条上即可。

轨迹运动

如何让轨迹动起来呢?其实就是每一帧都让轨迹沿着预设好的路线向前移动。设想轨迹有一个头和一个尾,每帧让头和尾都沿着路线移动固定的距离,再将头尾间的点连接起来即可。通常我们会用若干点来描绘一条路径(下面我们称这些点为路径点,即下图所示的实心点),点在路径上并不是均匀分布的,在转弯的地方会比较密集,直线的地方相对稀疏。所以在确认好头尾点的位置后,还需要将头尾点与中间的路径点串联起来,才是我们最终需要绘制的轨迹。如下图所示,红色曲线是需要绘制的部分。

所以如何定位首尾点的位置是重点。假设轨迹长为length,每帧向前移动x米,轨迹可见部分,即头尾长y米,第z帧的时候,head的位置就在距离起点xz米的地方,但如果xz>length,即head位置超过轨迹的总长度,则head的位置为length处,所以为min(xz, length);tail的位置需要判断有没有在可视范围内,所以值为max(0, xz - y)。

那么现在问题又归结到如何在路径上找到距离起始点特定长度的点的坐标。大家可能会想到使用turf的distance方法, 使用起来很方便,输入两个点的坐标就可以得到两点间的距离,所以一开始我们也是想到使用turf,这也是埋的一个坑。turf的性能很差,原因就是每次计算两点之间距离的时候都会重新计算已经计算过的点,造成了性能上的浪费,画不了几条长轨迹就会卡,所以另寻他法。后来我们采用动态规划的思路,方法如下:

以head为例,记录下上一帧head的位置即为上图中的lastPoint,以及于head最接近的路径点的位置p1,假设每帧需要移动的距离是dis,下一步就是判断lastPoint于p1的距离(设为d1)是否大于dis。如果d1 > dis,说明下一个点在lastPoint和p1之间,这样通过比例计算就能找到需要的位置;如果d1 < dis,说明下一个点在p1之后,接着让dis=dis-d1,然后判断p1和p2间的距离是否大于dis,以此类推就可以找到下一个顶点的位置。有了头尾的位置,再将头尾与路径点连接起来就可以得到当前需要绘制的轨迹。

数据更新

为了减轻前端的压力,我们将计算基本都移到了后端进行,例如轨迹的每次移动都是重新从后端获取的计算数据。考虑到数据更新非常频繁,我们使用websocket来传输数据,但是还是会存在一些问题。通常我们传输数据会使用json,但是对于北京屏的场景,json就不太适用。从动图中我们可以看到在北京屏的场景中,镜头一直在动,气泡一直在冒,轨迹一直在跑,所以稍微一个比较大的计算就会造成页面的卡顿,单个计算的时间需要控制在20ms内,一个JSON.parse()的使用就会明显感觉到页面的卡顿。所以在数据传输方面采用了ArrayBuffer。除了在传输格式上需要注意,另一个需要注意的点就是GC,对于float32Array等浮点型数据,需要提前分配好内存,否则又会出现页面的卡顿。

5. 气泡

如上图所示,不断冒出的气泡代表了各业务线订单的生成,成为了场景中不可或缺的一部分,感官上使北京屏更加丰盈。在开发过程中,设计师给的是.webp文件,开始时是想通过参考mapbox中添加动态marker的方式,我们将.webp文件以marker的方式添加到dom中。缺陷是这种情况下marker是不断重复轮播的,除非我们手动将对应的dom移除,否则页面上的marker会越来越多。但是我们如何判断每个marker一次动画播放结束从而将其移除呢?因为每个订单生成的时间不一致,所以动画开始和结束的时间不一致,很难判断。再者就算可以判断,频繁对dom进行添加和删除的操作也是欠妥的。最后我们将这些气泡以mesh的形式添加到scene中,在render的过程中不断更换气泡每帧对应的纹理来实现运动的效果。

确定位置

图(a)中o点是根据传过来的经纬度转换成的地图中的坐标。数据是以一组经纬度来代表订单生成的位置,我们要在这个位置显示气泡,webgl中是以一个三角形片元绘制各种图形,矩形至少需要两个三角形片元,6个顶点,除去可以共用的顶点,我们至少要知道图(b)中0、1、2、3四个顶点的坐标。如图所示,我们将经纬度对应的点作为矩形底边的中点,结合图片的宽高即可得到0、1、2、3四个点的坐标。

绘制纹理

图(a)中o点是根据传过来的经纬度转换成的地图中的坐标。数据是以一组经纬度来代表订单生成的位置,我们要在这个位置显示气泡,webgl中是以一个三角形片元绘制各种图形,矩形至少需要两个三角形片元,6个顶点,除去可以共用的顶点,我们至少要知道图(b)中0、1、2、3四个顶点的坐标。如图所示,我们将经纬度对应的点作为矩形底边的中点,结合图片的宽高即可得到0、1、2、3四个点的坐标。

上图展示了着色器的相关限制,与纹理相关的部分限制了片元着色器的纹理单元数量不超过16个,这就意味着如果我们将每个气泡单独绘制在一个canvas上,至多只能绘制16种类型的气泡,当然这对于我们当前的业务场景也是够用的,但是容量天花板相对较低。假设我们一个纹理单元绘制两种气泡,最多可绘制32种,以此类推。同时需要注意一下单个纹理单元的最大限制是16384,假设一张图片的大小是200*200,那么一个纹理单元最多容纳6561张图片。如果我们绘制的范围超过该限制,即要添加新的纹理单元,所以要注意边界判断。

在绘制纹理画布时,我们采用列主序的方式,maxTextureSize对应的就是前面提到的单个纹理单元的最大限制,height代表每张图片的高度。首先计算一列最多容纳的图片数,然后根据总图片数picNum得到纹理单元的行数heightNum和列数widthNum,根据以上信息,我们就可以精确的得到第m种气泡类型的第n帧图片在纹理画布上的第几行第几列,从而映射到气泡所在的位置。

气泡运动

我们将气泡运动分为两类,一类是需要重复运动的,一类是只运动一次的。这两种情况下我们都会记录当前气泡运动到哪一帧,区别是重复运动的气泡会从第一帧到最后一帧不断重复循环,因为气泡不会消失,所以重复运动适合于数据基本保持固定、不会随时间累加的场景。对于只运动一次的场景,除了记录气泡的当前运动的帧数showIndex,我们会额外设定个参数iconStartIndex记录当前动画从第几个气泡开始执行。假设当前数据中总共有100个气泡的信息,每次渲染时将所有气泡的showIndex加1并与总帧数frameNum比较,如果第n个气泡的值大于等于frameNum,则代表该气泡动画结束,且在该气泡之前的所有气泡动画也已结束,所以将n赋值给iconStartIndex,下次render时就会从第n+1个气泡的动画开始处理,而前n个气泡因为动画已结束所以忽略。

数据更新

为了保证数据的稳定性、减少气泡数据的请求次数,同时保持页面的丰盈性,我们采用了数据缓冲区,运行条件为该请求至少有一次是请求成功的。初始化时我们会备份三份数据,利用writeIndex和readIndex记录当前写入缓冲区与读取缓冲区数据的位置。数据更新时,请求回来的新数据会根据writeIndex依次取代对应位置的备份数据。同时为了让页面上的气泡效果更丰满,我们每隔几秒钟会从缓冲区中根据readIndex读取对应数据替换轮播的气泡数据。

6. 热力

热力图展示了以此时刻开始,过去一段时间内北京城的订单分布情况。我们将获取到的订单数据生成热力图并映射到建筑顶层,更加直观准确且及时的展示了全城订单分布。目前热力图可配置项包括:

  • 时间范围

可以展示过去一分钟、十分钟、一小时等任意时间间隔内的热力情况。

  • 半径大小

可以以任意距离为单位,该范围内的订单数对应的色阶上的颜色即为该范围的热力图颜色。

  • 色阶

热力图的颜色是可调的。

生成热力图

我们使用了现有的热力图库heatmap.js,使用起来也很简单(官网首页)。在此基础上,进行了二次开发使其更加适用于我们的大屏场景。下面简单介绍一下生成热力图的原理。

1)点模版

每个热力图数据点代表一个订单,会对应的生成如上图所示的点模版。根据之前配置的半径大小,会在灰度图上生成一个对应大小的渐变圆,根据可配置的模糊因子,可使圆点带有模糊效果。

2)灰度(透明度)叠加

rgb通道是无法线性叠加呈现效果的,但是透明度是近似线性的。根据第一步生成的数据点模版的比率,对应于透明度的值alpha,我们在canvas(shadowCtx)上绘制一个数据点,他们的透明度是可以叠加的,值越大,越不透明。

3)线性色谱

根据gradient生成自定义色谱,用于下一步着色。

4)着色

最后,透明度的叠加值映射到线性色谱,取线性色谱中的颜色为canvas上色就得到上图所示的最终的热力图。

映射热力图

最初的方案是将整个建筑都映射上热力的颜色,但是在我们的项目中,热力图的透明度从俯视角度开始是逐渐递减的,当镜头拉近到可以看清建筑时热力图已经消失,所以最后我们优化了之前的方案,只将热力颜色映射到了建筑的上表面。

调试工具

在调试过程中,因为灯光和屏幕的分辨率等因素导致最终在大屏上展示的效果和在台式机上的效果还是有一些差别的,加之设计师侧也不方便准确的描述差异,例如红色淡一些这些想象空间很大的描述,所以沟通之后开放了上图所示的半径和四种颜色的调试接口,方便设计师现场实时查看热力图的改动效果。

为了加快调参时热力图的响应,同时减少计算量,我们对原本的热力图代码做了一些修改。原来代码里和重绘有关的方法是repaint方法,但是调用该方法触发的操作是全部重新绘制,包括灰度图、线性色谱和热力图等。当我们只改变颜色时,是线性色谱发生改变从而着色时影响最终的热力图,灰度图并没有改变,所以我们添加了recolor方法,调试颜色时不会重绘灰度图。但是,如果改变了半径范围,还是需要调用原本的repaint方法,全部重新绘制。

参数解读

现在热力图已经绘制完成并映射到建筑上,那么热力图的代表的具体含义是什么呢?例如多大距离范围内到达多少单才能显示为图中的红色,代表高订单区呢?

我们根据屏幕的分辨率以及展示的地图范围,计算出1px对应约47.6390m。参数radius的值对应的就是像素数,所以我们可以根据radius的值来确定是以多大距离作为统计单位,例如radius=10.5对应的约是500m。

假设以方圆R范围作为统计单位,如果这个圆都显示红色,那么其透明度的叠加值要为1。点模版是一个渐变圆,圆心处透明度为1代表不透明,边缘处透明度为0代表完全透明。此外还有最大订单量设定的影响,假设我们将最大订单量设为MAX=10,那么每个点模版透明度的最大值即为0.1,所以点模版的透明度是从0~1/MAX变化的,不是从0~1变化的。设方圆R范围内的累计透明度为1所需要的订单量为x单,则有:

解得 x=3MAX,这意味着如果我们在程序中设定MAX的值来约束点模版透明度变化的范围,那么至少需要3MAX数量的订单才能是对应的范围展示为透明度为1对应的红色高订单区。(注:此方法是一个大概的计算,会存在一定程度上的偏差。)

7. 飞线

上图展示的是可视化大屏中飞线使用的两种场景,左边是直线,右边是曲线。这两种类型也是飞线最常使用的两种情况,实现起来并无很大差异,在开发过程中遇到的问题为飞线取点和绘制性能方面,下面简单说下。

直线

直线飞线的实现采用了较为常用的实现方式,为每条飞线创建一个sprite,通过控制sprite的scale、opacity和自定义的height等参数来控制飞线的长度、透明图和飞行高度。与常规使用不同的是,我们需要添加很多判定条件,因为飞线从不同的地方飞出,其长度和最大飞行高度是有限制的。如果我们采取统一标准,则会出现上半部分区域飞线过长、飞行高度过高以至于飞出屏幕,又或者下半部分区域飞线过短、飞行高度过低以至于存在感较低。最后我们采取来分段式限制,分为三个区域,上方飞线对应的随机长度和随机飞行高度范围较小,中间部分的始终,下方区域的范围相对较大,经过多次参数调整,最终达到了和谐的效果。

曲线

1)取点

曲线是由一系列的点连接组成,若想得到平滑的曲线,首先需要得到能生成我们想要的曲线的点。这里我们每条曲线是由100个点连接而成,那么如何得到这100个点呢?如果只是想在地球上两点之间生成一条曲线这本身不是难事,但是为了美观性,我们想要达到每条曲线的最高点距离地球的距离都是相同的,这样在地球转动或者各种视角下,视觉体验上才会比较好。为了实现这点,我们做了以下计算, Q代表的是飞线起点向量和终点向量之间的夹角:

Q < 30

此时我们使用三维三次贝塞尔曲线,根据起点startPoint、第一个控制点p1、第二个控制点p2、终点endPoint的坐标确定曲线,并利用其提供的getPoints方法得到曲线上的100个点用于绘制。

Q > 30

当角度大于30度时,我们发现得到的曲线两端变化较大,中间很长一部分几乎都是平缓的,整条曲线的弯曲变化不是特别顺滑。所以为了解决这种情况,我们对 Image的曲线采用了不同于三维三次贝塞尔曲线的方法,使用的是CatmullRomCurve3,即使用Catmull-Rom算法, 从一系列的点创建一条平滑的三维样条曲线,不仅限于两个控制点。我们采取的方案是根据起点和终点的经纬度差值、地球的半径和曲线最高点对应的半径进行插值计算,得到20个插值点的坐标,将这20个点传入CatmullRomCurve3中生成曲线,并利用其提供的getPoints方法得到曲线上的100个点用于绘制。

2)纹理映射

上一步我们获取的100个点是描绘一条完整曲线的全部点,但是从图7.1(b)中可以看出,飞线在飞的过程中展示的是完整的纹理,但是飞线长度只占总长度的1/3。如果按照正常的映射的话应该是完整的纹理对应完整的曲线,所以在飞的过程中应该只展示对应的纹理部分。那么如何实现动图中的效果呢?

第一反应就是改变顶点坐标,例如第一次render第0~30个点的范围,第二次render第10~40个点的范围,以此类推。效果确实是实现了,但是性能上有问题,由于频繁的更换顶点导致了页面的卡顿。

想到的第二种方案就是100个点全部绘制,在render的过程中z不断变换顶点对应的uv坐标。例如开始是第0~30个对应的uv坐标从0~1,下一次是第10~40个点对应的uv坐标从0~1,以此类推可以满足1/3长度的飞线展示完整的纹理,且不断移动。在这里还有个优化,一开始是在每次render的时候去遍历uv数组更改每个点对应的值,但是遍历数组也是耗性能的,特别是当数据量大的时候,后来是将一个常数传进了shader中用于计算每个点此时的uv坐标,每次render都会改变这个数,同时同步到shader中。

8. 散点

平时我们用静态的散点比较多,这种实现很简单。但当展示的信息量不是很丰满时就显得页面有些空洞,这时候就需要添加动态呼吸的散点,让页面看起来更热闹。我们在散点大小变化的过程中对应的改变其透明度,产生渐隐渐现的呼吸效果,同时我们可以随机点出现的位置,让效果看起来更灵动。

9. 结语

以上就是在全业务之城开发过程中的一些总结和思考,日后在不断开发数据可视化大屏的同时,我们也会加速沉淀通用组件库和搭建平台。

文章来源

转载说明:

  • 作者:滴滴技术团队
  • 版权声明:本文为「滴滴技术」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
  • 原文链接:https://mp.weixin.qq.com/s/eWifSqOvhHCqVul1tKDSlw