聊聊可视化

可视化

开发绝大多数 web 前端应用的时候,我们关心的是文字/多媒体、区块排版、布局、样式和交互,关心的是如何给用户『阅读』体验更好的『网页』。 而开发可视化应用,我们关心的则是数据计算、不止是区块的各种图形的绘图渲染,当然也少不了交互,希望通过一张图让用户看清数据背后隐藏的信息。可视化的本质也就是把数据组织起来(比如从非结构化变成结构化)之后绘图呈现。

可视化开发和渲染基本原理

可视化开发中我们可能会相对少的用到 html 和 css,更多用到 canvas、webgl、svg 之类的图形 api。因为可能要处理视觉细节,很多时候即时用了主流的可视化图或图表库,我们依然需要接触底层渲染层 api 精细控制图形渲染细节。

浏览器如何绘制图形

不管可视化和一般 web 前端开发有多少不同,最终都还是通过浏览器渲染呈现的。浏览器通过自身的渲染引擎绘制图形,主要有两类方式:

  • html/svg + css
  • canvas(canvas2d/webgl)

html/svg + css

可能提到可视化我们第一反应不会想到 html 和 css (实际中也极少用) ,但这个方式确实适合实现布局简单(区块式)的常规图表可视化。 这种方式主要靠 css 技巧,比如:柱状图可以通过 grid layout + linear-gradient 实现;饼图可以用 conic-gradient 圆锥渐变实现,折线区块图可以用 clip-path 拼多边形实现等等。

CSS
.pie-chart {
	display: inline-block;
	width: 100px;
	height: 100px;
	border-radius: 50%;
	background-image: conic-gradient(
		#000 60deg,
		#333 60deg,
		#555 120deg,
		#999 120deg,
		red 240deg,
		blue 240deg,
		#aaa 300deg,
		#eee 300deg,
		#ccc 200deg
	);
}

这里还提到了 svg,由于浏览器也支持直接内嵌 svg 标签,支持我们通过 dom api 操作 svg、css 处理 svg,同时提供了很多图形元素标签(html 中需要 css 实现)。所以 svg 本质上是一种有特殊属性的 dom ,也可以实现可视化,本质上跟用 html 实现没有区别。

由于 svg 基于 xml 语法,我们可以比较方便的做视图和数据的解耦:

HTML, XML
<rect x="0" y="0" width="20" height="40" fill="#666"/>

相比之下 css 至少需要理解一次比例转换:

CSS
/**
 data = [4, 4, 8]
 */

.bar {
	background: linear-gradient(to bottom, yellow 25%, blue 25%, blue 50%, red 50%);
}
  • 优点

    • 开发方便
  • 缺点

    • css 语法难以和数据解耦,难以拓展维护。
    • 一个 svg 元素只能表示一种图形,数据量大的时候回产生大量的 svg 元素,耗内存、影响性能。
    • 浏览器解析 html/svg 和 css 需要走一遍 构建 dom tree -> renderObject tree -> renderLayer tree 的渲染引擎流程,重渲会带来较大的性能开销(可视化重点在数据计算和绘图渲染)。

canvas(2d/webgl/webgl2)

提到 canvas 我们一般是指代浏览器的 canvas2d 上下文,而准确的说浏览器的 canvas elements 提供了 2d、webgl 和 webgl2 三种我们常用的上下文 api (其实还有 bitmaprenderer, placeholder, 这里不展开,感兴趣的可以看 html spec canvas context mode)。

从 api 规范角度看,canvas api 规范有 canvas2d 规范和 webgl 规范两类,webgl 规范是 opengl esopengl esopengl api 的子集,针对手机等终端设计)规范在 web 端的实现,webgl2webgl 分别对应 opengl es 3opengl es 2。最新的还有 webgpu,它是最新的 3d 图形 api,相比 webgl 有很大升级和差异,不再基于 opengl 而是 vulkan/direct3d,这里不展开讲。

不同于 html/svg 的声明式语法,我们通过 canvas api 是以指令式的方式绘图。canvas 元素在浏览器上创建画布并提供上下文接口,我们调用 api 绘制点线面登记本图形、设置图形属性、颜色、形状变换,从而完成绘图。

另一点, canvas2d 和 webgl 的绘制最终也都是给到 gpu 渲染 (大部分现代浏览器都会 gpu 加速,dom 渲染最后也是给到 gpu 位图),他们相比 html/svg 方式最大的不同是不存在 dom renderObjectTree -> renderLayer 的流程。

不过 canvas2d 和 webgl 对 gpu 的控制有所不同,我们无法通过 canvas2d 直接操作 gpu,我们只能调用 canvas2d 简陋的 api,浏览器在底层控制 gpu。webgl 预渲染到帧缓存后,再执行 webgl api (webglprogram)操作 shader,因此我们可以在这里控制 gpu 渲染,在 shader 中做更精细化的操作或计算,性能远超 js。尤其是对于图形数量庞大的绘制,我们可以利用 webgl 实例化渲染并让 gpu 并行处理实现批量渲染,而 canvas2d 在浏览器底层并不会主动做这种优化。

  • canvas 优点

    • 指令式语法,开发友好易于拓展。
    • 直接操作绘图上下文,没有 dom 渲染带来的性能问题。
  • canvas 缺点

    • canvas api 比较简陋
    • canvas 只有一张画布,没有图形元素的概念( html/svg 的 dom 对应某个基本图形元素),导致 canvas 中很难做到局部渲染、局部控制、事件操作等,会有整个画布重绘的问题。
    • 绘制图形数量大时也会有性能问题。
  • webgl 优点

    • 绘制图形数量大时、处理像素点多的精细绘制时,可以操作 gpu 调优
    • 3d 绘制,内置了 3d 投影和处理。
  • webgl 缺点

    • 设备兼容性问题
    • 开发成本

visual-0

native 如何绘制图形

还有点题外话,我们这里说的是 canvas elements 都是浏览器实现提供的,浏览器提供的东西最重要的就是通用性、跨平台、标准化。浏览器实现 canvas 至少要编译适配几个通用图形库,同时还需要再封装出一套 js api 给开发者用。

所以浏览器上 canvas 的绘制调用流程是:canvas js api -> js引擎 -> 浏览器接口逻辑 -> 图形库 -> gpu 绘制

比如 webkit 封装了 skia 图形库,chromium 会用 skiasoftware rasterizer 绘制成位图,作为纹理上传到 gpu,或由 skia 的 opengl 直接绘制纹理到 gpu (感兴趣可以看 GPU Accelerated Compositing in Chrome)。

但这一层层的封装避免不了性能消耗,尤其在 js 引擎这一换。而 native 可以直接调用 opengl 或其他底层接口。 所以 浏览器 canvas 的渲染表现会比 native 差很多。当然 native 也不具备浏览器的通用开放跨平台性。

可视化引擎/库需要做什么

可以看到在浏览器中,无论用哪种方式绘图都依然有缺点。首要缺点就是,浏览器是通用平台,通用平台都只会提供基础 api,不会提供跟应用场景强相关的高级 api(所以前面说 canvas api 简陋),而开发者实际中需要的是高级 api,比如 drawGraphdrawshape 之类的。这种高级 api 需要可视化引擎/库去实现。

因此最常见的是这种提供丰富绘制 api 的图表库,比如 echarts、g2;以及一些专业的图绘制,比如画地图地理的 mapbox、L7;还有一类无关应用场景、更灵活的绘制图形或物理模型,2d 的 spritejs、3d 的 threejs。让人不用通过 canvas2d/webgl 等底层 api 从基础图形画起。

前面提到可视化除了绘图之外的另一个关键是数据组织,所以还有一类专门用于数据处理、结构转换的框架,比如 d3.js。

事实上绝大多数可视化绘图(由于 2d 场景较多)都是基 canvas2d(3d 场景肯定是用 webgl)。 canvas2d 的两大缺点:

  • 一绘制图形数量大时会有性能问题,我们前面提了可以用过 webgl 并行批量渲染优化;
  • 另一大缺点难以局部渲染、局部控制、存在画布重绘,这些问题就需要引擎/库在内部重点处理了。

延伸阅读

我后来把这些 Canvas 与可视化思考用在了城市地图产品中,具体记录在《从 OpenStreetMap 到可编辑城市地图:我如何做 EveryCityMap》里。

CURRENTLY BUILDING

EveryCityMap

Style, share, and export custom maps for 500 cities. Choose a theme, edit the layers, and download a 1440px WebP without signing up.