1.5 用气泡图表示数量扩展x/y数据
像前面例子描述的传统离散型图表,只能展现x和y轴两个值之间的关系。有时两个值并不能恰当的展现出我们想要展现的数据。如果我们需要3个变量,我们可以使用一个离散型图表的框架来展现两个值,然后根据第三个值来改变图表中点的大小。那么使用气泡图就是最好的选择。
然而在使用气泡图时有一些需要注意的。像我们早先看到的饼图一样,人们非常不善于准确判断一个不是长方形形状的相对区域,所以气泡图不能让人们准确对比气泡的大小。但是,如果你只想展现一个大概的量而不是准确的量,那么使用气泡图是适合的。
在这次的例子中我们使用气泡图展现2005年卡特里娜飓风的路径。我们的x和y轴将会代表位置(纬度和经度),并且我们要确保我们的用户能准确地理解这些值。对于第3个值-气泡的大小-我们将使用风暴的持续风力,因为风速永远只是一个普通数值(并且风有时候大有时候小),所以使用持续风力是合适的。
就像本书1.1.1小节中一样,我们需要在我们的网站中包含Flotr2的类库,并且设置一个div元素来包含我们将要创建的图表。
1.5.1 第1步 定义数据
我们使用美国国家海洋和天气管理局(NOAA)的卡特里娜飓风的观察报告来做我们例子的数据。数据包括了飓风行进时的经纬度和每小时的风速。
var katrina = [
{ north: 23.2, west: 75.5, wind: 35 },
{ north: 24.0, west: 76.4, wind: 35 },
{ north: 25.2, west: 77.0, wind: 45 },
// Data set continues...
对于气泡图,Flotr2需要每个数据点是一个数组而不是一个对象,所以我们要建立一个简单的函数将源数据转换成需要的格式。为了使函数更通用,我们可以传参指定一个过滤函数。当我们提取数据点时,我们可以反转经线符号从左到右显示由西向东。
function get_points(source_array, filter_function) {
1 var result = [];
for (var i=0; i<source_array.length; i++) {
if ( (typeof filter_function === "undefined")
|| (typeof filter_function !== "function")
|| filter_function(source_array[i]) ) {
result.push([
source_array[i].west * -1,
source_array[i].north,
source_array[i].wind
]);
}
}
return result;
}
我们这段代码一开始在1的位置设置了一个返回值(result),是一个空数组。然后循环source_array,每次输入一个元素。如果filter_function参数是可用的,并且如果它是有效的函数,我们的代码会把源数组中当前元素作为参数在这个函数中进行调用。如果函数返回true,或者如果在get_points函数一开始的时候没有传filter_function函数参数,我们的代码会从源数组中的各个元素提取数据点然后push到result数组中。
正如你所看到的,filter_function参数是可选的。如果调用方省略它(或者不是一个有效的函数),那么在result中就是源的每个数据点。虽然我们现在并不马上使用过滤函数,但在这个例子后面的步骤中,我们就可以拿来就用了。
1.5.2 第2步 给图表创建背景图
因为我们图表中x和y轴的值表示的是坐标,用地图当作背景是再合适不过的了。为了避免涉及到版权的问题,我们使用stamen设计网站上的地图图片,使用OpenStreetMap网站的数据。这两者分别在Creative Commons CC 3.0和CC SA授权下可用。
当你使用地图时投影就成了棘手的问题,除了映射的地区更小以外,缺少有效的映射也是个问题,映射的地区都在图表中心的边缘。对于这个例子,我们将采取墨卡托投影来展示地图,在相对小的区域聚焦。假设我们已经将x、 y轴和经纬度进行了转换。
1.5.3 第3步 绘制数据
一开始我们还是先从使用最少选项开始写代码,尽管这样我们需要迭代几次才能得到想要的图表。我们需要一个参数来指定气泡的半径。对于像这个例子中的静态图表,最简单的方法就是用几个值尝试找出最合适的尺寸。在我们的例子中使用了0.3这个值。除了前面的选项以外, draw方法还需要一个包含图表的HTML元素和数据本身。
Flotr.draw(
document.getElementById("chart"),
[{
data: get_points(katrina),
bubbles: {show:true, baseRadius: 0.3}
}]
);
正如你所看到的,我们使用了前面写的转换函数来从源数据中提取需要的数据。这个函数的返回值直接被传递到draw方法的第二个参数中。
目前,我们还不用操心背景图片的问题。我们只需要把调整过的数据添加到图表中就可以了。图1-22中就是目前为止显示的效果,尽管还需要改进,但我们已经迈出了第1步。
1.5.4 第4步 添加背景
现在我们来看一下Flotr2是如何在背景图上绘制数据的。同时我们还想要做一些别的事情。首先我们添加一个背景图,移除掉网格线。其次,不显示轴线的标注;经纬线对于普通用户来说没有什么意义,地图对于用户来说也不是必需的。最后,也是最重要的,我们需要调整缩放图表来匹配地图图片。
Flotr.draw(
document.getElementById("chart"),
[{
data: get_points(katrina),
bubbles: {show:true, baseRadius: 0.3}
}],
{
1 grid: {
backgroundImage: "img/gulf.png",
horizontalLines: false,
verticalLines: false
},
2 yaxis: {showLabels: false, min: 23.607, max: 33.657},
3 xaxis: {showLabels: false, min: -94.298, max: -77.586}
}
);
我们在1的位置添加了一个grid的选项来告诉Flotr2不展示横纵网格线,并且指派一个用背景图片代替。我们想要图片展示的纬度值从23.607°N到33.657°N,经度值从77.568°W到94.298°W。所以在2和3的位置我们将这些值提供给xaxis和yaxis选项来当作的范围值,并且将两个轴的标注设置为不显示。需要注意的是因为我们涉及到的经度是西经,所以需要使用负值。
在图1-23中看图表这些点效果还不错。我们不仅可以清楚的看到飓风的路径还能看到飓风的强弱变化。
1.5.5 第5步 给气泡上色
在这个例子中,我们通过选项来修改气泡的颜色,可以让用户不用过于耗费精力就能获取到更多的信息。让我们在每个观察点上加上能表明萨菲尔-辛普森飓风等级的数字。
这里我们可以利用我们前面做的数据格式化函数来过滤一下要展现的数字。因为萨菲尔-辛普森飓风等级是基于风速的,所以我们要基于风的属性来过滤。例如下面的代码展现了只将风速74英里/小时到95英里/小时的值提取出来作为1级飓风等级。我们传给get_points函数的是返回为true的风速。
cat1 = get_points(katrina, function(obs) {
return (obs.wind >= 74) && (obs.wind < 95);
});
我们用下面的代码来将数据划分到多个集合中,让Flotr2来分配不同的颜色给每个集合。除了5级飓风以外,我们已经解析出了热带风暴和热带气压的强度。
Flotr.draw(
document.getElementById("chart"),
[
{
data: get_points(katrina, function(obs) {
return (obs.wind < 39);
}),
color: "#74add1",
bubbles: {show:true, baseRadius: 0.3, lineWidth: 1}
},{
// Options continue...
},{
data: get_points(katrina, function(obs) {
return (obs.wind >= 157);
}),
color: "#d73027",
label: "Category 5",
bubbles: {show:true, baseRadius: 0.3, lineWidth: 1}
}
],{
grid: {
backgroundImage: "img/gulf.png",
horizontalLines: false,
verticalLines: false
},
yaxis: {showLabels: false, min: 23.607, max: 33.657},
xaxis: {showLabels: false, min: -94.298, max: -77.586},
legend: {position: "sw"}
}
);
如你在图1-24中看到的,我们在左下角已经给飓风等级添加了标注和图例。
1.5.6 第6步 调整图例的样式
默认情况下,Flotr2会让所有元素看起来都尽可能的大。在图1-24中的图例就是一个好例子:它看起来很局促且不够吸引人。幸运的是,修改这个很简单:我们简单添加一些CSS的padding样式就可以了,我们也可以设置图例的背景颜色来和Flotr2的背景形成反差来突出。
.flotr-legend {
padding: 5px;
background-color: #ececec;
}
为了防止Flotr2给图例创建背景色,我们将透明度设置为0。
Flotr.draw(
document.getElementById("chart")
// Additional options...
legend: {position: "sw", backgroundOpacity: 0,},
// Additional options...
经过最后的调整,图1-25就是我们最终完成的样子。我们不想使用Flotr2的选项来指定标题,因为Flotr2将标题等也算在图表内容区内,所以当字数较多时,会将图表空间压缩收窄(并且我们也不能预知用户浏览器的字体大小)。这会导致纬度的变形。所以简单的用HTML标签来盛放标题就可以了。
气泡图相比二维的离散图表增加了其他维度。事实上,和我们的例子一样,它也可以进一步添加两个维度。在例子中使用气泡尺寸来代表风速,颜色表明飓风的分类。这两个值都需要关注。人们既不善于对比两个维度的区域,也不能轻松的对比相对的形状和颜色。气泡图从来都不应该用来表达一个临界数据或精确的数量。在例子中气泡图表现的很好,既不用准确的表示风速,也不用精确的指定飓风的位置。几乎没有人能区分出100/小时和110米/小时的差别,但他们能知道达拉斯和奥尔良的区别。
1.5.7 第7步 Flotr2“bugs”的应急预案
请参考本书1.1.9小节中关于创建基本柱状图表中是如何解决Flotr2类库的一些“bug”的。