第三章 处理数据
5.过滤数据
想象一下你需要过滤d3选择的已经和可视化元素相关联的数据集,根据客户的输入数据信息显示或者隐藏某些可视化元素,D3提供了这种过滤方法来完成这种类型的数据可视化驱动,在本节中我们将展示这种方式是如何实现的。
来看下面的代码。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Data Filter</title> <link rel="stylesheet" type="text/css" href="styles.css"/> <script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script> </head> <body> <script type="text/javascript"> var data = [ // <-A {expense: 10, category: "Retail"}, {expense: 15, category: "Gas"}, {expense: 30, category: "Retail"}, {expense: 50, category: "Dining"}, {expense: 80, category: "Gas"}, {expense: 65, category: "Retail"}, {expense: 55, category: "Gas"}, {expense: 30, category: "Dining"}, {expense: 20, category: "Retail"}, {expense: 10, category: "Dining"}, {expense: 8, category: "Gas"} ]; function render(data, category) { d3.select("body").selectAll("div.h-bar") // <-B .data(data) .enter() .append("div") .attr("class", "h-bar") .append("span"); d3.select("body").selectAll("div.h-bar") // <-C .data(data) .exit().remove(); d3.select("body").selectAll("div.h-bar") // <-D .data(data) .attr("class", "h-bar") .style("width", function (d) { return (d.expense * 5) + "px";} ) .select("span") .text(function (d) { return d.category; }); d3.select("body").selectAll("div.h-bar") .filter(function (d, i) { // <-E return d.category == category; }) .classed("selected", true); } render(data); function select(category) { render(data, category); } </script> <div class="control-group"> <button onclick="select('Retail')"> Retail </button> <button onclick="select('Gas')"> Gas </button> <button onclick="select('Dining')"> Dining </button> <button onclick="select()"> Clear </button> </div> </body> </html>
上面的例子展示了通过用户对数据分类的选择,来对该数据对应的可视化元素进行高光显示。当我们选择Dinning按钮时上面代码显示的效果如下图:
在本例中,数据集是一个包含了消费费用和消费类别的个人消费信息记录,我们可以从代码标记的A处看到,在B、C、D处我们可以看到采用了前面讲过的D3用的enter-update-exit模式,来定义了一组水平的div元素与我们定义的数据集进行绑定。目前为止这些代码和前面例子中的并没有什么大的区别。接下来我们来看看E处
d3.select("body").selectAll("div.h-bar")
.filter(function (d, i) { // <-E
return d.category == category;
})
.classed("selected", true);
d3 selection.filter方法以函数作为参数,该函数施加于所有的选择元素中,本例作为参数的方法有2个参数和一个隐藏的对象引用:
d:当前对象绑定的数据元素。
i:当前对象的索引。
this:隐藏的对当前的dom对象的引用。
d3 selection.filter方法期望参数方法返回布尔类型的值,如果返回的值为真,相应的元素会包含到filter方法的新的选择中,在本例中,filter方法返回符合用户选择的消费类,然后对对应于该类方法的可视化元素设置css属性,这种方式可以快速的过滤数据,并产生新的选择,允许用户对这些新选择做进一步的修改和操作。
提示:
d3 selection.filter方法对待其参数的返回值采用的是javascript的真假机制,也就是不是严格的布尔类型值,比如false、null、0、“”、undefined、NaN都被认为是false,其他的被认为是ture。
6.分类数据
在很多情况下,有必要根据其绑定的数据对你的可视化元素进行分类,这样可以突出重点,直观的找出数据的不同的地方。在本节中我们将展示d3是如何实现这种效果的。
打开编辑器,输入下面的代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Data Sort</title> <link rel="stylesheet" type="text/css" href="styles.css"/> <script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script> </head> <body> <script type="text/javascript"> var data = [ // <-A {expense: 10, category: "Retail"}, {expense: 15, category: "Gas"}, {expense: 30, category: "Retail"}, {expense: 50, category: "Dining"}, {expense: 80, category: "Gas"}, {expense: 65, category: "Retail"}, {expense: 55, category: "Gas"}, {expense: 30, category: "Dining"}, {expense: 20, category: "Retail"}, {expense: 10, category: "Dining"}, {expense: 8, category: "Gas"} ]; function render(data, comparator) { d3.select("body").selectAll("div.h-bar") // <-B .data(data) .enter().append("div") .attr("class", "h-bar") .append("span"); d3.select("body").selectAll("div.h-bar") // <-C .data(data) .exit().remove(); d3.select("body").selectAll("div.h-bar") // <-D .data(data) .attr("class", "h-bar") .style("width", function (d) { return (d.expense * 5) + "px"; }) .select("span") .text(function (d) { return d.category; }); if(comparator) d3.select("body") .selectAll("div.h-bar") .sort(comparator); // <-E } var compareByExpense = function (a, b) { // <-F return a.expense < b.expense?-1:1; }; var compareByCategory = function (a, b) { // <-G return a.category < b.category?-1:1; }; render(data); function sort(comparator) { render(data, comparator); } </script> <div class="control-group"> <button onclick="sort(compareByExpense)"> Sort by Width </button> <button onclick="sort(compareByCategory)"> Sort by Category </button> <button onclick="sort()"> Clear </button> </div> </body> </html>
本例继续使用前面例子用到的数据和结构,然后对数据进行分类排序,在这个例子中我们将对我们创建的div元素,根据其对应数据的进行分类和排序。我们对div进行宽度排序的效果如下所示:
本例A、B、C、D处和前面并无差别,一旦框架打好,在E处我们选择所有的div,然后调用sort()方法进行分类和排序。本例中是sort()方法的参数是两个比较函数
var compareByExpense = function (a, b) { // <-F
return a.expense < b.expense?-1:1;
};
var compareByCategory = function (a, b) { // <-G
return a.category < b.category?-1:1;
};
比较函数接收两参数,返回-1,1,或者0 。如果返回的是-1,那么a就是在b的前面,如果返回的是1,那么a就在b的后面,如何返回的是0,说明a和b相当,他们2个的位置会随意放置 。sort()方法返回一个新的已经排好序的选择。可以对该选择进行进一步的操作。
7.从服务器载入数据
一般情况下你很少会只使用静态的数据来进行可视化,数据可视化通常是依靠服务器程序生成的动态数据来进行可视化。正因如此,D3也设计了很多方便的工具来完成这些任务,在本节中我们将会看到,D3的数据如何被动态的加载,相应的可视化元素也动态的进行更新。
本节中我们就要用到前面说过的服务器程序了,我本地安装的是wamp集成环境,这个比较简单,可以根据自己习惯来选择。
那么我们再编写的代码文件,就要房子服务器的文件夹中,在wamp环境中末日是放在wamp安装目录的www文件夹里面
首先在里面建一个json数据,命名为data.json
[ { "expense": 15, "category": "Retail" }, { "expense": 18, "category": "Gas" }, { "expense": 10, "category": "Retail" }, { "expense": 25, "category": "Gas" }, { "expense": 66, "category": "Retail" }, { "expense": 70, "category": "Gas" }, { "expense": 44, "category": "Dining" }, { "expense": 13, "category": "Dining" }, { "expense": 20, "category": "Dining" }, { "expense": 12, "category": "Retail" }, { "expense": 15, "category": "Gas" } ]
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Load JSON Data Feed</title> <link rel="stylesheet" type="text/css" href="styles.css"/> <script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script> </head> <body> <div id="chart"></div> <script type="text/javascript"> var data = [ // <-A {expense: 10, category: "Retail"}, {expense: 15, category: "Gas"}, {expense: 30, category: "Retail"}, {expense: 50, category: "Dining"}, {expense: 80, category: "Gas"}, {expense: 65, category: "Retail"}, {expense: 55, category: "Gas"}, {expense: 30, category: "Dining"}, {expense: 20, category: "Retail"}, {expense: 10, category: "Dining"}, {expense: 8, category: "Gas"} ]; function render(data) { d3.select("#chart").selectAll("div.h-bar") // <-B .data(data) .enter().append("div") .attr("class", "h-bar") .append("span"); d3.select("#chart").selectAll("div.h-bar") // <-C .data(data) .exit().remove(); d3.select("#chart").selectAll("div.h-bar") // <-D .data(data) .attr("class", "h-bar") .style("width", function (d) { return (d.expense * 5) + "px"; }) .select("span") .text(function (d) { return d.category; }); } render(data); function load(){ // <-E d3.json("data.json", function(error, json){ // <-F data = data.concat(json); render(data); }); } </script> <div class="control-group"> <button onclick="load()">Load Data from JSON feed</button> </div> </body> </html>
当点击load Data fom JSON feed按钮后的效果如下:
在本例中我们在A处定义了一个静态数据,然后采用前面的框架实现可视化,当用户点击按钮时d3会从服务器载入data.json文件,d3会完成对文件的解析,然后调用render方法完成可视化。d3还支持对csv、tsv、txt、html、xml等类型文件的加载。当然这并不是 唯一可以加载数据的方式,D3对其他库的支持也是非常高,你也可以利用jquery或者zepto来创建ajax请求来加载数据。