且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

《JavaScript数据可视化编程》—— 第1章 图像数据1.1 创建基础的柱状图

更新时间:2022-08-30 19:20:15

本节书摘来自异步社区《JavaScript数据可视化编程》一书中的第1章,第1.1节,作者: 【美】Stephen A.Thomas 译者: 翟东方 , 张超 , 刘畅 责编: 陈冀康,更多章节内容可以访问云栖社区“异步社区”公众号查看。

第1章 图像数据

JavaScript数据可视化编程
在很多人的印象中,数据可视化图形是一些非常酷炫复杂、充满科幻设计感的图形。这种看法其实存在误区。实际上,建立一个有效的数据可视化模型并不需要特别深厚的设计功底和复杂的编程技巧,如果你一直牢记着数据可视化的目的是帮助人们更好地理解数据,那么你就会认同,在进行数据可视化的过程中最需要注意的,恰恰是“简单”二字。那些看似简单基础、随处可见的图表及其所传达的信息,往往最容易为人们所理解和消化。

因为用户已经熟悉了各式各样的常规图表,如柱状图、折线图、坐标图等。用户很容易理解这类图表的形式和数据代表什么意思,所以他们可以毫不费力地从图表中提炼出有用的信息。如果想让用户迅速明白你的数据,我建议***还是采用一个简单、静态的图表。这样,你可以省下大量的时间,用户也可以花更少的精力来理解你的意图。

有许多高质量的工具和JS库可以帮助你,下面从制作一个简单的例子开始,让你踏上数据可视化设计之路。使用这些工具,你可以避免重复造***,还能找到很多资料。本书随后会介绍几个类似的工具,但就这一章而言,我们将会使用Flotr2 这个库。使用Flotr2,可以很容易在任意网页上添加标准柱状图、线图和饼图,并且还支持一些不是那么常见的普通图表类型。随后,我们会结合实例了解Flotr2到底能做些什么。你将学到以下内容。

如何创建一个基本的柱状图。

如何用折线图绘制连续数据。

如何用饼图强调百分比。

如何用散列图绘制二维数据。

如何用气泡图展示二维数据的量的对比。

如何用雷达图显示多维数据。

1.1 创建基础的柱状图

如果你不确定什么类型的图表能体现你的数据,那你首先应该考虑是否可以做柱状图。柱状图这种形式,我们已经司空见惯了,但是它真的是一种非常有效的图表形式。柱状图通常可以表现数据的变化过程,或者表示多个数据之间的差异。下面我们从建立柱状图入手来开始我们的练习。

1.1.1 第1步 引入所需的JavaScript代码
我们使用Flotr2这个JavaScript库来创建图表。首先,我们需要把Flotr2这个JavaScript库引入到我们的网页中。因为Flotr2现在还没有特别好的cdn源,所以你需要下载一份拷贝,并放到自己的服务器上。我们这里使用这个库的压缩版本,即flotr2.min.js来做我们的代码依赖。

使用Flotr2之前,我们不需要引入其他的JavaScript库(比如jQuery),但是Flotr2必须依赖于HTML5的canvas元素的支持。当然,主流的现代浏览器(Safari、Chrome和Firefox)以及IE9以上都已经支持canvas属性了,但是,现在我们仍然有数百万的用户在使用IE8 (甚至更早的浏览器)。为了支持这些用户,我们可以在页面中引入一个额外的库(excanvas.min.js),这样,这些老浏览器也能支持canvas元素了。

现在,我们先写下下面的一段HTML文档结构。

<!DOCTYPE html> 
<html lang="en"> 
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>  
   <body> 
     <!-- Page Content Here --> 
1    <!--[if lt IE 9]><script src="js/excanvas.min.js"></script><![endif]--> 
     <script src="js/flotr2.min.js"></script>
   </body>
 </html>

因为现代浏览器不需要引入excanvas.min.js,所以我们需要在代码1处将引入的script代码做一个HTML注释处理,以保证只有IE8及IE8更早的浏览器版本会加载它。此外需要注意的是,我们的库文件需要在HTML文档的最后引入,以保证浏览器在加载JavaScript文件之前可以优先渲染DOM树。

1.1.2 第2步 创建一个用来包含图表的

元素
在引入Flotr2的JavaScript文件之后,我们需要在HTML文档中创建
元素来包裹住这个图表。Flotr2要求这个
元素必须指定它的宽高,图表才能够被建立起来。我们可以在CSS样式表中设置元素的width和height属性,或者在
标签上通过内联样式来定义,只要保证CSS代码能够生效即可。下面的例子采用内联方法指定了
的CSS样式。
<!DOCTYPE html> 
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title></title> 
  </head> 
  <body> 
    <div id="chart" style="width:600px;height:300px;"></div>
    <!--[if lt IE 9]><script src="js/excanvas.min.js"></script><![endif]--> 
    <script src="js/flotr2.min.js"></script>
  </body> 
</html>

也许你发现了,我们给这个

指定了一个明确的ID: “chart”,以便之后我们可以通过这个ID来引用此元素。

这样,我们就拥有了一个简单的代码框架。在本书的第1章中,你即将学到的其他图表的制作方法,也都是基于这个简单的代码框架来制作的。

1.1.3 第3步 定义数据
有了代码框架,我们就可以研究怎么显示数据了。在本例中,我会试图去统计在过去7年的英超联赛中曼城队的获胜场次。当然,你想把数据换成其他的也是可以的。你可以把数据直接写在JavaScript中(像下面的例子一样),或者用其他方法(比如向服务器发AJAX请求获取),这两种方法都行。

<script> 
var wins = [[[2006,13],[2007,11],[2008,15],[2009,15],[2010,18],[2011,21], 
             [2012,28]]];
</script>

现在,我们建立了三维数组,然后,我们来研究它。

在使用Flotr2建立的图表中,每一个独立的数据都是通过数组中x和y这2个值来唯一确定的。在我们的例子中,我们用x表示年,y表示获胜场次。接下来,我们把若干个这样的x、y的组合使用一个外层数组进行嵌套,这个用来嵌套的数组,我们称为序列。接着,我们在这个序列的外面又嵌套了一个外层数组,以便将来我们可以在其中存储多个序列。但是现在我们仅仅需要一个序列就够了。

关于数组每一层的定义,记住下面3条即可:

数组第一层:每个独立的数据自身是一个数组,包含x和y两个值;

数组第二层:若干个独立数据在一起构成数组,称为序列;

数组第三层:若干个序列构成供Flotr2渲染图表使用的完整数据,形式也是数组。

1.1.4 第4步 绘制图表
以上我们就把我们需要的数据准备好了。如下所示,通过简单调用Flotr2库,我们的第一个图表就要建立好了。

window.onload = function () { 
    Flotr.draw(
        document.getElementById("chart"),
        wins,
        { 
            bars: { 
                show: true 
            }
        }
    );
 };

上面代码的关键点在于window.onload,因为我们需要在文档加载完成后调用函数。 window.onload事件触发后,我们执行Flotr.draw这个函数,并传3个参数给它。这3个参数包括:包含图表的HTML元素本身,刚才定义的图表数据和一些可配置的图表选项。在本例中,我们设置的选项的意思是告诉Flotr2创建一个柱状图。

因为Flotr2不需要依赖jQuery,我们在这个例子中没有用jQuery的$等操作符来进行操作。如果你的页面已经包含了jQuery,也可以使用jQuery方法来改写上面的代码。

图1-1所示就是你在网页中看到的图表。
《JavaScript数据可视化编程》—— 第1章 图像数据1.1 创建基础的柱状图

现在你有了一个柱状图,但是这个图表还有很多改善空间。让我们逐渐添加一些选项让这个图表看起来更好。

1.1.5 第5步 改进纵轴
图1-1中,最明显的问题是纵轴的刻度。 Flotr2默认将数据中最大值和最小值自动设为坐标轴的取值范围。在曼城的年获胜场次的统计中,最小值出现在2007年,其只获得了11场胜利,所以,Flotr2非常忠实地将纵轴的最小值设置成了11。可是通常在柱状图中,***是将纵轴的最小值设置为0。如果不是0的话,用户会在对图表的理解上产生迷惑。比方说有一个人只是扫了一眼图1-1的图表,就得出结论说曼城在2007年一场比赛都没有赢。这种情况显然是需要避免的。

图1-1中,纵轴的格式也是一个问题。因为Flotr2会默认精确到小数点后一位,所以会在所有标注上带一个多余的“.0”。我们可以通过设置一些纵轴的选项来修复这两个问题。

Flotr.draw(document.getElementById("chart"), [wins], { 
    bars: { 
        show: true
    },
    yaxis: {
        min: 0,
        tickDecimals: 0 
    }
});

Flotr.draw函数通过min属性来设置纵轴的最小值,并且通过tickDecimals属性告诉Flotr2在标注中要展示的小数精度。在我们例子中我们不想要小数位,所以将这个值设为0。

正如你在图1-2中看到的,在配置了这些选项后,纵轴的起始值被设置为0,所有数字格式变为整数,明显改善了纵轴的效果。

《JavaScript数据可视化编程》—— 第1章 图像数据1.1 创建基础的柱状图

1.1.6 第6步 改进横轴
和纵轴类似,在Flotr2中,横轴的标注也被默认为拥有1位小数的数字。因为我们图表中,横轴数据的单位是“年”,我们此处可以像设置纵轴一样,也通过tickDecimals属性将横轴的小数精度设置为0。但是这种做法并不通用。如果当x的值不是数字类型(比如队名)时,这种解决方案就行不通了。为了能适应更普遍的情况,我们首先需要改变下数据结构,建立一个新的数组years,在这个数组中,每一个年份有一个索引数字配对。同时,我们修改之前的wins数组,将原来的年份使用对应的索引数字替代,这样在两个数组之间就建立起了查询
关系。

var wins = [[[0,13],[1,11],[2,15],[3,15],[4,18],[5,21],[6,28]]]; 
var years = [
   [0, "2006"],
   [1, "2007"],
   [2, "2008"],
   [3, "2009"], 
   [4, "2010"], 
   [5, "2011"],
   [6, "2012"] 
];

正如你看到的,我们在wins数组中,使用简单的0、 1、 2等数字替换了x值的实际年份。然后,我们在新定义的years数组中将这些整数映射到对应的字符串上。我们这里的字符串映射为年份数字,如果需要,也可以以任何字符串代替。

另外一个问题是两个柱体之间缺乏间距。在默认情况下,每一个柱体是平均分配整个横轴的的长度的,但是会显得过于拥挤。我们可以用barWidth属性进行调整。把这个属性的值设置到0.5,这样每个柱体就只占据原空间的一半了。

解决上面两个问题的具体配置见下面的代码。

Flotr.draw(document.getElementById("chart"), wins, { 
     bars: {
         show: true,
         barWidth: 0.5
     }, 
     yaxis: {
         min: 0,
         tickDecimals: 0
     },
     xaxis: {
1      ticks: years 
     }
 });

注意,在代码1处,我们对x轴使用了ticks属性,这是用来告诉Flotr2把x轴的标注通过years数组和x值进行匹配。现在我们可以在页面上看到如图1-3展示的图表。 x轴标注的是对应的年份,柱体之间有间距,这些都改善了图表的易读性。
《JavaScript数据可视化编程》—— 第1章 图像数据1.1 创建基础的柱状图

1.1.7 第7步 调整样式
现在,图表的功能性和可读性的调整已经完成,接下来,我们可以花一些精力把图表做得更炫一些。我们打算为图表添加标题,去掉不需要的网格线,再调整一下柱体的颜色。

Flotr.draw(document.getElementById("chart"), wins, { 
    title: "Manchester City Wins",
    colors: ["#89AFD2"],
    bars: { 
        show: true, 
        barWidth: 0.5,
        shadowSize: 0,
        fillOpacity: 1,
        lineWidth: 0 
    },
    yaxis: { 
        min: 0, 
        tickDecimals: 0 
    },
    xaxis: {
        ticks: years 
    },
    grid: { 
        horizontalLines: false, 
        verticalLines: false
    }
});

如图1-4中所见,我们现在有了一个曼城粉丝可以引以为豪的柱状图了。

《JavaScript数据可视化编程》—— 第1章 图像数据1.1 创建基础的柱状图

对于任何中等大小的数据集,标准的柱状图常常是最有效的可视化图表。用户对这种形式已经熟悉了,所以他们不必花费额外的努力去理解形式。这些柱体在视觉上和背景对比强烈,并且通过高度不同来体现数值之间的差异,所以用户很容易抓住突出的数据。

1.1.8 第8步 多彩的柱体色彩
到目前为止,尽管我们图表的颜色单一,但是,我们正是利用了单一颜色,展示了同一类型的值(曼城胜利场次)在不同时间的变化,所以表义是很清晰的。

柱状图除了可以表示连续的数据发展趋势外,也能有效地进行多个数据之间的对比。举个例子,假设我们想要展示一年中多个球队的总胜利场次。这种情况下,每个球队的柱体就需要用不同颜色来代表。让我们再去把图表加工一下。

首先我们需要稍微调整一下数据结构。

之前我们只在图表中展示了一组数据,即一个球队逐年的获胜场次统计,数组名称为wins。现在,我们想让图表显示多个球队同一年的数据,并且为每个球队提供一个独立的颜色,所以,我们需要定义一个新数组wins2。下面的例子中可以看到wins和wins2两个数组之间的对比。注意这里的数组结构变化。同样,我们把每个柱体原来的标注从“年份”替换成了球队名称的缩写。

var wins = [[[0,13],[1,11],[2,15],[3,15],[4,18],[5,21],[6,28]]]; 
var wins2 = [[[0,28]],[[1,28]],[[2,21]],[[3,20]],[[4,19]]]; 
var teams = [
    [0, "MCI"], 
    [1, "MUN"],
    [2, "ARS"],
    [3, "TOT"],
    [4, "NEW"]
];

组织好我们的数据后,接下来我们就可以让Flotr2绘制图表了。图表绘制完成之后,我们可以看到,除了每个球队的颜色不同以外,其他的一切都和之前的图表在形式上保持一致。

Flotr.draw(document.getElementById("chart"), wins2, { 
    title: "Premier League Wins (2011-2012)",
    colors: ["#89AFD2", "#1D1D1D", "#DF021D", "#0E204B", "#E67840"],
    bars: { 
        show: true,
        barWidth: 0.5,
        shadowSize: 0,
        fillOpacity: 1,
        lineWidth: 0
    }, 
    yaxis: {
        min: 0,
        tickDecimals: 0
    }, 
    xaxis: { 
        ticks: teams
    }, 
    grid: { 
        horizontalLines: false,
        verticalLines: false
    }
});

在图1-5中可以看到,通过一些小的调整,我们的柱状图就转而表现了另外一种不同类型的数据。我们之前使用这个图表表现了一支球队在不同时间的数据趋势,现在,我们同样可以用柱状图表示多支球队在同一时间的数据对比。由此可见柱状图虽然形式简单,但是数据表现力极强。

《JavaScript数据可视化编程》—— 第1章 图像数据1.1 创建基础的柱状图

为了方便说明,本书在讲解时,给大家看的都是代码片段。
1.1.9 第9步 Flotr2可能会出现的一些“bug”及处理方案
如果你正在使用Flotr2为一个大型网站创建复杂的内容,你可能会遇到一些讨厌的“bug”。我这里在“bug”上加了一个引号,是因为Flotr2处理的方式虽然是正确的,但是显示效果却未必总是正确的。在构建图表的过程中,Flotr2会创建一个虚拟的HTML元素,以便用来计算尺寸大小。 Flotr2为了让这些元素在页面中不可见,会通过调整这些元素的CSS定位,来让它们从屏幕的可视范围内消失。但是,有时Flotr2的处理方式会出现一些问题。在flotr2.js的2281行中,有下面一段CSS定义:

D.setStyles(div, { "position" : "absolute", "top" : "-10000px" });

Flotr2打算把这些虚拟的HTML元素放置到距离浏览器顶部10 000像素的位置。然而,CSS的绝对定位是基于父层的相对定位基础上的,所以如果你的文档超过了10 000像素,这些HTML元素所包含的文本和数据就会让你的页面乱掉。在Flotr2的代码官方改进这个问题之前,有2种方法可以解决这个bug。

一种方式是你自己修改代码。 Flotr2是开源的,所以你可以免费下载完整的源码来进行适当的修改。这样只要简单地修改虚拟HTML元素的定位,将它改到除上方之外其他很远的位置(左边或右边)就可以了。

如果你是个有强迫症的处女座,觉得改变库的源码让你不舒服,另一种方式是你自己找到并隐藏这些虚拟元素。这里需要注意的是,在你最后一次调用Flotr.draw()之后再去做这些。在最新版本的jQuery中,可以用下面的代码来消除这些无关紧要的元素。

$(".flotr-dummy-div").parent().hide();