5.3 带时间属性的比例
我们也经常会遇到带有时间属性的比例。在一次民意调查中,每个问题都会有多个答案呈不同比例,而同样的民意调查有可能在一年中的每一个月都会举行。我们不仅关心单次调查的结果,还希望看到大众的观点会如何随着时间而变化。一年前的调查结果和今天相比存在哪些差异?
当然,并不仅仅是民意调查会出现这种情况。各种比例分配都会随着时间而变化。在下面的例子中,我们来看看1860—2005年间美国人年龄结构的分布变化。随着卫生条件的改善和家庭平均人口的减少,整体的人口寿命与之前一代相比已经有了显著的提高。
5.3.1 堆叠的延续
假设你有多个时间序列图表,现在将它们从下往上堆叠,填满空白的区域。你得到的就是一个堆叠面积图,水平轴表示时间,垂直轴的范围为0~100%,如图5-20所示。
图5-20 堆叠面积图的基本框架
如果对这个面积图表进行垂直切片,就会得到该时间片段上的比例分布情况。或者你也可以把它看作是按时间相连的一系列堆积柱形图。
1.创建堆叠面积图
有多种方法可以绘制这一图表,我们首先尝试用Illustrator。面积图工具(Area Graph Tool)可以帮助我们直接生成堆叠面积图(参见图5-21)。
图5-21 面积图工具
在新文件中按住左键并拖动鼠标,在弹出的电子表格中输入数据。现在你已经熟悉载入数据、生成图表、完善设计这一整套流程了,对吧?
在输入数据之后,你会看到一个堆叠面积图,如图5-22所示。
图5-22 Illustrator中默认生成的堆叠面积图
图5-22中的面积图超出了100%线。出现这种情况是因为堆叠面积图并没有采用标准化的比例,也就是一组累加等于100%的值。所以如果你希望每个时间段的比例总和是100%,就需要对原始数据进行标准化。图中的错误是我的责任,我把有些数据输错了,不好意思。修改过来之后,你看到的应该是图5-23中的样子。当然,你可能在第一次就正确地输入了数据,那么你就已经得到这一图表了。
图5-23 修改后的面积图
不过还是要注意这类事情。最好能在一开始就检查出所有的笔误或者数据条目中的问题,不然等到设计完成之后再回过头来查找错误将是非常痛苦的事情。
提示 在手工输入数据时一定要小心。在数据的格式转换过程中,很多低级错误都可能发生。
现在基础图形已经没问题了,让我们去掉坐标轴和线条。使用直接选择工具选中想要的元素。我倾向于去掉垂直轴,然后留下较细的刻度线,以保持干净、轻巧的感觉。同时在数字后面加上百分百符号,这样表意更加明确。此外我通常都会把图形的边线从默认的黑色改为白色。最后为图形加入深浅不同的蓝色。效果如图5-24所示。
图5-24 修改后的颜色
再次重申,这只是我的设计品位,你可以按自己的喜好进行调整。颜色的选择同样也会受到图表主题的影响。你设计的图表越多,这方面的感觉就会越好。
提示 使用适合于主题的颜色,同时通过不同的色调来引导读者的视线。
是不是还少了点东西?嗯,水平轴上还没有标签。现在加上它们,并为各个面积区域添加标签,指明各个年龄段(参见图5-25)。
图5-25 添加标签后的堆叠面积图
我在图表的右侧也添加了注释。我们最感兴趣的是年龄段的变化,虽然可以从图表中了解到变化的趋势,但实际的数字更能说明问题。
最后,添加标题、文字说明,并在底部注明数据来源。稍微调整右侧注释的颜色,赋予更多的含义。现在我们得到了最终的图形,如图5-26所示。
图5-26 最终的堆叠面积图
2.创建可交互式堆叠面积图
堆叠面积图的缺点之一是当类别和数据点过多时,它们就会很难阅读,体现不出多少价值。在年龄段划分中这一图表类型很有效,是因为只有5个类别。如果类别继续增多,各层就会逐渐被压缩成细条。类似地,如果某个类别所占的数值比较小,那么它就很容易会被其他更加“粗壮”的类别挤得无处容身。不过,如果堆叠面积图是可交互的,这一问题就不存在了。
通过交互,读者可以自由搜索各类别,而且针对感兴趣的点可以缩放坐标轴进行细致的观察。文本提示可以帮助读者查看那些面积过小、无法放置标签的层的具体数值。简而言之,你可以将那些不适于静态堆叠面积图的数据应用于可交互式图表,从而便于读者浏览及探索。我们可以通过Protovis用JavaScript来实现,不过出于了解更多工具的目的(因为这一过程确实很有乐趣),这次让我们试一下Flash和ActionScript。
►Martin Wattenberg的NameVoyager让可交互式堆叠面积图广受欢迎。它会显示各时期婴儿的起名趋势,而且当你在搜索框内输入姓名后,图表会自动刷新。访问http://www.babynamewizard.com/voyager进行体验。
说明 在线可视化目前已经开始从Flash向JavaScript和HTML5缓慢迁移,但并不是所有的浏览器都支持后者,比如说较早版本的Internet Explorer就不支持。此外,由于Flash已经流行很多年了,相比使用原生的浏览器功能,Flash的大量元件库和工具包能使任务变得更简单。
幸运的是,你不必一切都从零开始。通过由加州大学伯克利分校可视化实验室开发并维护的Flare可视化工具包,绝大部分工作都已经为你完成了。Flare是一个ActionScript库,实际上是一个名为Prefuse的Java可视化工具包的移植版本。我们会先看一下Flare网站上的一个应用案例JobVoyager,它和NameVoyager非常类似,但探索的是人们从事的职业。在你搭建好开发环境之后,剩下的就只是接入你自己的数据以及定制外观了。
说明 访问http://flare.prefuse.org/免费下载Flare,并解压到目标目录中。
你可以用ActionScript编写完整的代码,然后编译进Flash文件中。这意味着你先用自己理解的语言编写代码,然后通过编译器将代码翻译成计算机(或者说Flash播放器)能明白的比特编码,这样它才能服从你的命令。所以你需要两个东西:写代码的地方,以及编译的途径。
比较麻烦的做法是用某个标准文本编辑器来写代码,然后利用Adobe的免费编译器。我说它麻烦是因为其过程比较迂回,而且你必须先在计算机上安装很多东西。
比之更为简单、我鼎力推荐的做法是,如果你有不少项目都打算用到Flash和ActionScript,那么就使用Adobe的Flex Builder。它能把ActionScript编程中沉闷的部分加快解决,因为你写代码、编译和调试都在同一个地方。不过Flex Builder需要花钱购买(对学生是免费的)。如果你不确定是否值得花钱,也可以先下载免费试用版本,之后再作决定。对于我们这个堆叠面积图实例而言,下面将会解释在Flex Builder中的操作流程。
说明 在本书写作时,Adobe已经将Flex Builder改名为Flash Builder。这两者很相似,但也存在少许变化。以下步骤中虽然使用的是前者,但使用后者也是一样的操作(2)。访问http://www.adobe.com/products/flashbuilder/下载Flash Builder。确保能充分利用面向学生的折扣。只需提供你的学生证复印件,就能得到免费下载许可。或者你也可以找稍旧的Flex Builder版本,价格稍低。
当你下载并安装了Flex Builder之后,运行它,会看到一个窗口,如图5-27所示。
图5-27 运行Flex Builder后的初始界面
在左侧的Flex Navigator(包资源管理器)控件内单击右键,在弹出菜单中选择Import(导入)。你会看到一个弹出窗口,如图5-28所示。
图5-28 Flex Builder中的Import窗口
选择General(常规)下的Existing Projects into Workspace(现有项目到工作空间中)并单击Next(下一步)按钮。在Select root directory(选择根目录)单选框处单击Browse(浏览)按钮找到你存放Flare文件的地方,并选择Flare目录,同时确保在项目窗口中的Flare已被选中,如图5-29所示。
图5-29 “已有项目”窗口
单击Finish(完成)按钮。然后再重复一遍刚才的导入操作,这次选择的是flare.apps目录。在当前的包资源管理器中展开flare.apps/src/flare/apps/文件夹,并双击JobVoyager.as。现在你的FlexBuilder窗口应该类似于图5-30。
图5-30 打开后的JobVoyager代码
如果你现在单击运行按钮(界面左上角带有白色播放三角形的绿色按钮),你就会看到运行后的JobVoyager,如图5-31所示。这表示你已经完成了最困难的部分:安装。现在我们只需要插入自己的数据,然后按自己的喜好进行调整即可。这个过程听起来很熟悉吧?
图5-31 JobVoyager应用(另见彩插图5-31)
图5-32显示了我们最后要实现的效果。它显示了1984—2008年间美国的消费开支状况,数据来源于美国人口统计局。水平轴显示的依然是年份,但图标表现的不是职业类别,而是开支分类,例如住房(Housing)和食品(Food)。
图5-32 有关消费开支的可交互式堆叠面积图(另见彩插图5-32)
►访问http://datafl.ws/16r体验最终的可视化效果,并观察各种交互方式。
现在你需要改变数据源,它在JobVoyager.as的第57行进行了指定。
- private var _url:String = "http://flare.prefuse.org/data/jobs.txt";
把_url改为指向我们提供开支数据的地址http://datasets.flowingdata.com/expenditures.txt。和jobs.txt一样,这一数据也是以制表符分隔的文件。第一列是年份,第二列是类别,最后一列是消费额度。
- private var _url:String =
- "http://datasets.flowingdata.com/expenditures.txt";
现在文件将读取我们的消费开支数据,而不再是职业方面的数据了。到目前为止都还比较简单。
下面的第58和59两行是第一列的数据,在本例中也就是原始数据文件(jobs.txt)中列出的不同年份,是从1850年到2000年、以10年为单位进行的划分。你可以编写代码让程序自动在载入的开支数据中找到年份,不过由于这一数据不会变化,所以我们也可以节省点时间,直接明确指定数据的各个年份即可。
开支数据是从1984年到2008年、以年为单位进行划分的,所以我们把第58和59两行进行相应的修改。
- private var _cols:Array =
- [1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992,
- 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
- 2003, 2004, 2005, 2006, 2007, 2008];
下一步修改涉及数据的标头。原始的数据文件(jobs.txt)共有4列:年份(year)、职业(occupation)、人数(people)和性别(sex)。开支数据中只有3列:年份(year)、开支类别(category)和消费额度(expenditure)。你需要让代码适应新的数据结构。
幸运的是,这很容易。年份列和之前是一样的,你只需要把人数改变为消费额度(对应垂直轴)、把职业改变为类别(对应各层)即可。最后,把所有用到性别的地方都删除。
第74行的作用是重塑数据,以便呈现堆叠面积图的形式。它将职业、性别两者指定为类别(也就是说,有职业和性别两种层),然后在x轴调用年份,在y轴调用人数。
- var dr:Array = reshape(ds.nodes.data, ["occupation", "sex"],
- "year", "people", _cols);
把代码改成这样:
- var dr:Array = reshape(ds.nodes.data, ["category"],
- "year", "expenditure", _cols);
在我们的数据中,只会用到一个类别(没有性别层),也就是开支类别(category)。x轴依旧显示年份,而y轴是消费额度。
第84行将数据按照职业(按首字母顺序)和性别(以数字表现)排序。现在我们只需要按照开支类别排序:
- data.nodes.sortBy("data.category");
你现在是否有点概念了?基本上所有代码都已经呈现在你眼前,你只需调整一些变量以适应新的数据即可。
提示 可视化领域中有很多优秀的开源项目。虽然现在你仍对编写代码“谈虎色变”,但在很多情况下可以借用这些项目中已有的代码来表现你自己的数据,你的工作只是调整一些变量而已。挑战在于读懂代码,理解它们是如何运作的。
第92行的作用是通过性别对各层进行着色,但我们并未通过性别来分割数据,所以不需要这样做。删除整行代码:
- data.nodes.setProperty("fillHue", iff(eq("data.sex", 1), 0.7, 0));
稍后我们会回头再调整各层的颜色。
第103行的作用是根据职业为各层添加标签:
- _vis.operators.add(new StackedAreaLabeler("data.occupation"));
我们希望的是根据开支类别来添加标签,所以进行相应改动:
- _vis.operators.add(new StackedAreaLabeler("data.category"));
第213~231行处理JobVoyager的过滤。首先设置的是男性/女性的过滤,然后是职业的过滤。我们不需要前者,所以可以删除第215~218行,然后使第219行成为一个简单的if语句。
与之类似,第260~289行(也就是删除if条件之前的第264~293行)创建了按钮来触发男性/女性的过滤。我们也可以删除它们。
现在我们基本上快把职业数据全部改为自己的开支数据了。回到第213行的filter( )函数。像之前一样更新该函数,以按开支类别而不是职业过滤。
现在的第218行(也就是删除if条件之前的第222行)是这样:
- var s:String = String(d.data ["occupation"]). toLowerCase( );
把occupation改为category:
- var s:String = String(d.data ["category"]). toLowerCase( );
下一个需要调整的是颜色。如果你现在开始编译代码并运行,就会得到一个红色系的堆叠面积图,如图5-33所示。而我们需要让对比再增强一点。
图5-33 基础色调的堆叠面积图(另见彩插图5-33)
颜色在两个地方指定。第86~89行指定了边框颜色,并且对所有元素都使用了红色:
- shape: Shapes.POLYGON,
- lineColor: 0,
- fillValue: 1,
- fillSaturation: 0.5
之后在第105行通过计数更新了色彩的饱和度(红色的级别)。其中SaturationEncoder( )的代码在第360~383行。我们并不打算用不同的饱和度来区分各层,而是明确指定各层的颜色值。
首先,按以下代码更新第86~89行:
- shape: Shapes.POLYGON,
- lineColor: 0xFFFFFFFF
现在lineColor已经指定了边框色为白色。如果开支的类别比较多,可能不应该这样做,因为那样会显得比较杂乱。现在类别并不多,所以用白色边框可以提高图表的易读性。
下一步,建立一个颜色的数组,这些颜色是根据级别进行排序的。在第50行前加入以下代码:
- private var _reds: Array = [0xFFFEF0D9, 0xFFFDD49E, 0xFFFDBB84, 0xFFFC8D59,
- 0xFFE34A33, 0xFFB30000];
我是用ColorBrewer(之前提到过)来找到这些颜色的,该工具能根据我设置的条件提供用色建议。它本身是用于地图选色的,但对于一般的可视化也很适用。
现在在第110行添加一个新的ColorEncoder:
- var colorPalette:ColorPalette = new ColorPalette(_reds);
- vis.operators.add(new ColorEncoder("data.max", "nodes",
- "fillColor", null, colorPalette));
说明 如果在编译代码时出现错误,看看JobVoyager.as文件的前面部分是否指定了以下两行用于导入ColorPallete和Encoder对象的代码。如果没有的话,添加进去。
- import "are.util.palette.*;
- import "are.vis.operator.encoder.*;
啊哈!现在再运行应该就能得到我们想要的效果了(参见图5-32)。当然,你不必止步于此,还有很多事情可以做。你可以接入自己的数据,改变配色方案,或者进一步定制来满足自己的需求,比如改变字体或文本提示的格式。甚至你还可以和其他工具整合,或者加入更多的ActionScript代码等。
5.3.2 逐点详述
堆叠面积图也存在缺点,比如每一个层的变化趋势可能会难以识别,因为每一个数据点的位置都受到了它下方点的影响。所以有时候用直接的时间序列图来表现分布反而会更直观一些。
幸运的是,在Illustrator中这两者很容易转换。数据的输入方法是一样的,所以你只需改变图表类型即可。在人口年龄结构的问题中,如果一开始选择折线图工具而非面积图工具,你就会得到如图5-34所示的默认图表。
图5-34 默认的折线图
按之前的方法,根据自己的喜好对图表和格式进行调整、简化,这样针对同一数据我们就有了另一种视角(参见图5-35)。
图5-35 简化并添加标记后的折线图
有了这个时间序列图表,更容易看出每一个年龄段各自的变化趋势。但是,图表丧失了整体感,也没有体现出其中的比例分布状况。选择何种图表应该反映出你希望表达的重点是什么。如果页面上有足够的空间,你甚至也可以把两种图都放上去。