数据可视化溯源之<图形语法>
图形和语法
要了解图形语法,首先要知道什么是“图形”,什么是“语法”。图形(Graphics)的定义非常广泛,但是在我们这里主要表示 “由计算机生成的用于展示数据的图表”。而语法(Grammar) 就不必多说了,学过英语的都懂,就是一套“体系内元素的组合规则”。
避开分类的视角——Graphics 与 Charts 的区别
在图形语法学中,我们使用 Graphics 而避免使用 Charts 的概念。这两个概念很难翻译,Charts 更接近一类通用的图表类型,而 Graphics 的概念更接近“制图学”。
我们经常会把统计图叫做“图表”或者“Chart”,比如饼图叫 pie chart,柱状图叫 bar chart 等等。在图形语法学中,我们尽量避免用分类学的眼光来看待这些图,因为我们通过简单的形状这各人不同的经验来区分图表类型是无法做到覆盖所有情况的。很多图表库和 BI 工具会直接把各种图表分类列出来让你直接选择,但是有些图表的分类会有冲突。
图1: ECharts中的饼图类型
比如图1中是非常普及的图表库 ECharts 中的实例页面。左侧排布着各种图表的类型,点击以后右侧会显示这个图表类型下的多个子类型。当我们选中饼图后,发现它包含了饼图、环图、南丁格尔玫瑰图等几个子类型。
图2:Echarts中的树类型
同理,图2中展示的是树形图的类型,主要用来表达层级结构。其中就包含了辐射状布局的树形图。
那么我们很容易想到一种问题,比如图3中的这种“太阳辐射图”(sunburst)的形式,到底应该算是一种饼图还是一种树形图呢?
图3:ECharts 中的太阳辐射图
如果光从认知方式上去考虑这个分类问题,很难回答,不同的人会有不同的看法。ECharts 的解决方案是,专门开一个和饼图、树形图同等级别的分类,就叫做 sunburst。这也是一个合理的方法,但这是因为 sunburst 相对来说还是一个比较常用的图表类型,而类似这样的归类难题太多了,很多不常见的图表你更难处理。
这样的基于用户认知判断的图表分类方法,也许在制作简单工具或者开发界面的时候很有用,但是对于我们深入理解图形而言毫无帮助。
"Some classifications have been attempted based on cluster analyses of ordinary users' visual judgments of similarities among real statistical graphics (e.g., Lohse et al., 1994). This approach may be useful for developing interfaces but contributes nothing to a deeper understanding of graphs."
-- Leland Wilkinson
因此,我们在图形语法中使用 Graphics 的概念,而避开更偏类型学的 Chart 这个概念。我们尝试去探究图表的本质,比如,饼图其实本质上就是一个绘制在极坐标系内的分割柱状图。一旦我们理解了这个层面,即便是很冷门的图表类型甚至是你自己发明的新图表我们也可以去构建了。
"Charts are usually instances of much more general objects. Once we understand that a pie is a divided bar in polar coordinates, we can construct other polar graphics that are less well known."
-- Leland Wilkinson
面向对象的设计
计算机相关从业者一定对“面向对象”的概念不陌生。它是一种抽象方法,一种设计思路,把一个系统的组成部分看成各个“对象”,每个对象有自己的属性和操作方法。具体的概念很难三言两语讲清,不了解的读者可以自行搜索。
在图形构建系统中利用面向对象的设计思路,可以提升系统的灵活性和可复用性。另外,面向对象的设计(OOD,Object-Oriented Design)本身就是一种自然而然的思考图形的方式,因为图形本身就很形象,很容易看成“对象”。
"ODD is a natural framework for thinking about graphics because graphics are objects."
-- Hurley and Oldford, 1991
面向对象的制图系统
在数学上,“图”(graph)其实是一个抽象的概念,是不可见的,它代表了点的集合。而“图形”(graphic)则是图的物理化表现,是可见的。这种有形的展现形式其实是用美学属性来实现了抽象的图概念。
"A graph is a set of points. A mathematical graph cannot be seen. It is an abstraction. A graphic, however, is a physical representation of a graph. This representation is accomplished by realizing graphs with aesthetic attributes such as size or color."
-- Leland Wilkinson
所谓的图形语法,其实就是为了构建出一个面向对象的制图系统。或者说,图形语法是为了这个系统制定的基本规则。
制图的三大步骤
在一个面向对象系统中,图形可以是一个对象,但它本身也是位置、颜色、坐标等对象的集合。如果这些对象间的信息沟通可以遵循一套统一的图形语法,那么整个系统将会呈现高度的一致性和灵活性。
为此,我们将介绍创建图形的三大主要步骤:
- 规格(Specification)
- 装配(Assembly)
- 展示(Display)
1. 规格 Specification
Specification 这个词表示“规格”、“详述”、“介绍”、“说明书”。在这一步骤,我们将图形的属性规范地用一种形式语言来描述。
用户在前端可能不需要了解或者看到这种描述,但是从系统角度来说,系统应该用这种方式来做底层构建。
直接上例子,一般我们将统计图表的规格用六项声明来描述:
- DATA:从数据集中创建出变量的一系列数据操作
- TRANS:变量变换(比如:排序)
- SCALE:缩放变换(比如:取对数)
- COORD:坐标系(比如:极坐标、笛卡尔坐标)
- ELEMENT:图(比如,点集)和他们的美学属性(比如:颜色)
- GUIDE:参考物(比如:坐标轴、图例)
图4:图形规格的6项声明
2. 装配 Assembly
一个场景画面和它的规格描述还是有很大不同的。为了依据规格描述绘制出画面,我们必须要处理它的几何、布局、美学属性等等来使它的场景被精准地渲染出来。
一个统计图的制图系统和体系必须要能够从一份规格说明装配成一幅真正的图表。
3. 展示 Display
为了让我们可以感知信息,装配好的图表本身必须显示在某一种显示介质上,比如屏幕、纸张、视频、全息影像等。
我们现在讨论的更多的,是在计算机相关平台上的展示。这里面还牵扯到很多动态的交互的展示方式,比如缩放、连接、下钻、拖选等等。
举个例子
说了这么多抽象的概念和步骤,不如来个例子。
图5:不同国家的死亡率/出生率对比图
图5是一张1990年27个不同国家内死亡率和出生率对比的图表。
从分类学上我们挺难描述出它是怎样一张图。是散点图?热力图?等高线图?还是混合图?如果要让你用一段文字来描述这张图,然后把图遮住,给你的同事看这段文字,让他把图形还原出来,你有把握能写出这么清晰的描述文本吗?
规格
让我们来看看用 Specification 如何描述这张图。
图6:图5的图形语言规格描述
首先我们在这份规格描述中省略了 DATA、TRANS、SCALE、COORD 的部分,因为这些部分要么非常直接明显(比如数据),要么没有做操作(比如没有涉及到变量变换),要么直接使用了默认的方式(比如直角坐标系)。我们仅对 ELEMENT 和 GUIDE 进行分析。
首先我们看到图上分布着各个国家的名字,每个名字其实代表了一个点(point),这个点的位置(position)是由 birth 和 death 两个数据变量以及横纵坐标轴来决定的。我们这里没有真的把那个点画出来,所以可以认为点的尺寸(size)是0。而那些表示国家名称(country)的文本,则是用来标记这些点的标签(label)。综上,第一行 ELEMENT 的声明是:
其他行的细节就不一一解释了,在以后的文章中我们会详细展开介绍。
这样的规格描述,交给系统程序以后,程序就可以完全理解你所表达的是什么样的图形了。
装配
当我们把这张图的规格交给制图系统以后,制图系统会梳理出这张图的对象结构,如下图。
图7:图5的设计结构树
三角箭头表示的是“下一级对象‘是一个’上一级对象“,比如坐标轴(Axis)是一个参考物(Guide)。
菱形箭头表示的是“上一级对象‘有一个‘下一级对象“,比如坐标轴(Axis)有一个标签(Label)。
从这个装配结构我们可以看出,这一张图表的聚合组成结构。而程序也正是通过这些结构去组合和渲染图形的。
展示
有了上面的装配结构,我们还需要一些列的渲染工具,比如符号、线段、多边形等。同时,我们还需要一些布局设计规则。然后我们就能在相应的介质上展现出我们的图形了。
系统灵活性
依据以上的图形语法制作的制图系统拥有很高的灵活性。比如,我们想把刚才图5的图表变成下图的密度热力图,只需要做简单的变换。
图8:替换后的热力图
将之前的两个 ELEMENT 替换为现在的这个再去掉一个 GUIDE,就可以了。至于核密度算法我们只要调用规定好的smooth.density.kernel.epanechnikov方法就好了。
如果要替换成图9的形式,也是一样,相应变换规格中的声明就可以了。导出世界地图的经纬度数据,坐标轴替换为mercator投影后的地图,我们可以直接把具体国家的地理信息也展示出来了。
图9:替换后的地图