原文地址:https://geekplux.com/2019/10/06/how-to-implement-data-visualization

题目想了半天,最后定了这个,也不知道有没有准确表达我的意思。其实本文主要想分享我做数据可视化这么久之后积累的一些经验,重点在技术实现这个层面。

在已经确定要实现一个可视化视图之前,你一定已经经历了思考和设计,拿到了一张确定的设计图或者原型图,所以这里就不赘述可视化在实现前的那些步骤了,直奔如何编程实现的主题。

实现前的规划

拿到一张设计图之后,和做前端切图差不多,你基本上要先做规划:

  • 整个视图可以切分成几部分可能一个视图包含了条形图、折线图、热力图和非常复杂的网络图等,这些混杂在一起的时候,切分视图很重要。如何保证实现起来方便。而且由于他们之间可能还有联动,所以如何设计共享的数据状态也很重要,否则后期会出现很多硬编码的情况(例如直接全部遍历高亮改颜色之类的操作)这种动不动循环很容易时间复杂度上O(n2)及以上,影响性能。
  • 一共有多少种元素哪些元素是在上层,哪些是在下层。每种元素画出来的时候需要几种数据。比如画圆形的时候你除了考虑圆心位置还要考虑半径是表达哪个数据维度、颜色又是哪个、饱和度是哪个
  • 元素之间的链接,语义或位置上的联系是什么最常见的比如两个矩形之间的线段是怎么连上的,是在中心位置还是顶点位置。还有哪些元素是可以 group 在一起的等等。

举个例子

以下图为例:

visualization design demo

为了简单,例子中其他视图的部分我剪裁掉了,只保留了一副 Sankey 图,文字部分也打码,但应该还是可以看出结构的。基本上元素有矩形、小圆、曲线、文字。矩形在最上层,有 hover 和 click 交互。线段和矩形都是半透明,线段在矩形下层。矩形之间的连接靠的是和它相邻的小圆点。文字分两部分,年份和矩形下面的字段。那么经过分析可以做出如下判断:

  • 年份和其对应的横线应该是最下层,年份之间间隔固定。那只需设置一个常量就可以算出这些年份文字对应在画布中的位置。
  • 矩形、它下面的小圆点和文字可以作为一个 group,在视图中的位置可以统一计算,然后再计算这个 group 里元素的相对位置。
  • 这个 group 的位置可以由左上顶点和右下顶点来确定(即两个 x,y)。另外该位置应该是根据其跨越的年份算出。所以需要一个 linear scale 来映射它携带的年份数据和年份在画布上的高度。
  • 线段是最常规的三次贝塞尔曲线,只需要起点和终点两个 x, y 就可以算出。不需要额外的坐标了。

计算、计算、还是计算

经过刚才的分析和举例,你应该也看得出其实算坐标就是可视化中最重要的事。各种点各种 x,y 算来算去,算出坐标之后的渲染、样式都是水到渠成。一般计算也分几个步骤。这些步骤不一定是按顺序的,而且实现的时候你还可以不断根据需求优化调整。

1 数据结构设计

这个我上文也略微提到,只有你数据结构设计得好,才能降低实现的难度并有效提升性能。

通常是数组就搞定一切了,但复杂的可视化中像 tree 和 graph 这种经典的数据结构也经常会被用到。无需掌握太复杂的,基本操作就够用了。当遇到性能瓶颈时,因为这些数据结构比较经典,你也有很多资料可参考去优化它。

2 比例尺 (scale)与映射

数据可视化中坐标、大小、形状、颜色、饱和度等都有可能映射不同的数据维度,所以这些都是可计算的部分,而且它们之间有时候还会互相影响。

比例尺有很多种,类别型(离散型)、有序型、数值型数据通常用到的比例尺也不一样。线性比例尺(linear scale)应该是最常用的,其他的比例尺可以参考 GitHub - d3/d3-scale: Encodings that map abstract data to visual representation. ,每种比例尺用处不同,具体是用在哪种数据类型或哪种图元类型,感兴趣的你可以进一步学习一下。

3 坐标计算

坐标计算刚说过是可视化中最必不可少的环节,通常是一些简单的数学计算,就是加减乘除啦 :) 算线性坐标(斜线)时可能会用到二元一次方程,算曲线可能会用到二次或高次方程,能回忆起并用上初中高中的知识有时候是很有趣的事。

4 交互计算

交互计算一般就是事件驱动了,在回调函数中进行数据的再处理,进行二次计算后渲染出来就完成了一段交互。数据量少的时候可以一次次遍历,完全不用担心性能。如果数据量巨大,你可以采取空间换时间的方法,在相关交互的元素中互相存储引用,这样只要 O(1) 就可以完成交互。

5 动画计算

一般可视化都有动画成分。用 Chrome Developer Tool 录屏观察这些动画,就发现它们一般都是沿着一个轨迹去运动,所以动画的计算就是去算出这些轨迹,然后再插值(interpolate)

这里稍微解释一下插值: 插值你可以理解成在两个数字之间按一定规律插入一系列数字。比如起点坐标是 (0, 0),终点坐标是 (10, 10),那可以在其中插值 (1, 1), (2, 2), (3, 3)… 等,也可以插入 (1, 1), (2, 2), (4, 4), (8, 8)…等,计算机去按这些插入的值不停的计算再渲染,就变成了动画。显然这个点按后一种插值方式运动的速度更快,这样我们肉眼会看到它是以一个加速度去运动到终点的。不同的插值方式造成了不同的动画效果。

计算举例

这些一系列计算的例子可以参考我之前的博客 数据可视化之 Sankey 桑基图的实现 - GeekPlux 。其实不管什么图都大同小异啦。

渲染

计算出所有需要的坐标之后就可以去渲染了,让其画到画布上。在 Web 上一般可以靠 HTML + CSS、SVG、Canvas、WebGL 去画,它们的能力互有交叉,你可以根据需求选择。

选择时除了要考虑性能问题,还要想想数据表达和实现成本。最简单的例子:如果你是用 HTML 和 CSS 去渲染元素,那么上下层关系你可以简单地通过 z-index 去控制,而如果用 SVG 去渲染,那只能通过元素在 DOM 树中的前后顺序去控制了,用 WebGL 去表达这种就复杂了,深度缓存、Blending 啥的,3D 可视化还要算 z 轴坐标。

渲染好像没多少说的,更底层的渲染机制我们没必要了解,除非涉及到更难的可视化吧,至少我现在还没碰到。

这是一篇经验贴。如果按参加工作时间开始算,我也是个六年经验的 code monkey 了。现在愈发觉得实际编码前的思考和规划更为重要,编码只是实现中最简单的环节,帮你把思考的结果表达出来而已。


本作品采用知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议进行许可。