第二章 选择器
在这一章中我们将学习以下几个方面
1.选择单个元素
2.选择多个元素
3.循环访问一个选择集
4.子(部分)选择器
5.方法链条
6.操作原生选择器
引言
你想要采用d3编写任何可视化程序最基础也是最重要的任务就是选择器。选择器帮你定位网页上确定的可视化元素,如果你熟悉w3c标准的css选择器,或者jquery,zepto的选择器,那么很好。你会觉得对d3的选择器非常熟悉和亲切。如果你之前没有用过也不用担心。本章将逐步讲解d3的选择器是如何使用的。
现代浏览器对选择器的支持已经被w3c标准化,但是传统的w3c选择器接口标准在开发web程序和可视化程序的时候存在其局限性,传统的选择器只是帮你选择而并没有任何操作,意思就是说,传统的选择器帮你选择出了元素,但是如果你要对元素进行操作可能还需要再遍历整个选择的集合再添加相应的对每个元素的操作。看下面的代码:
var i = document.querySelectorAll( p). iterator();
var e
while( e = i.next()){
// do something with each element selected
console.log(e);
}
这段代码的第一行选择了所有的p元素,然后循环访问执行方法iterator(); 这样很明显提高了效率,特别是当你需要操作大量不通的元素的时候,这个在数据可视化中就会经常遇到。这就是为什么d3要定义他自己的选择器,在下面的部分我们将来介绍d3选择器的简洁和强大之处。
在介绍d3选择器之前,先要说说css3选择器,如果你已经非常熟悉css3选择器可以跳过此部分,d3选择器是建立在css3选择器的基础之上或者说基于css3选择器。我们在这里介绍一下css3选择器最基础的语法规则以便让我们更容易理解d3选择器的工作原理。
#foo :选择id为foo的元素,比如<div id="foo"></div>
foo : 选择所有标签为foo的元素,比如<foo>
.foo :选择所有类为foo的元素,比如<div class="foo">
[foo=goo] : 选择所有foo属性的值等于goo的元素,比如<div foo="goo">
foo goo : 选择所有在标签foo下的goo元素, 比如<foo><goo></foo>
foo#goo : 选择标签为foo的元素中id的值等于goo的元素,比如<foo id="goo">
foo.goo : 选择标签为foo的元素中class的值等于goo的元素,比如<foo class="goo">
foo:first-child :选择所有标签为foo的元素中的第一个元素, 比如<foo>//这一个<foo><foo><foo>
foo:nth-child(n) : 选择所有标签为foo的元素中的第n个元素,
比如<foo>
<foo>//foo:nth-child(2)
<foo>//foo:nth-child(3)
<foo>
css3选择器是一个非常复杂的课题,这里我们只列出了一些常用的语法规则,理解这些会对你学习d3有很大的帮助,如果想要掌握更多的css3选择器可以参考w3c的css3选择器API(http://www.w3.org/TR/css3-selectors/)
小提示:如果你使用的老版本的浏览器并不支持d3的选择器,你可以去下载一个小插件解决这个问题下载地址(http://sizzlejs.com/)。最新css4标准也正在制定之中可以到这里查看(http://dev.w3.org/csswg/selectors4/)
在浏览器中输入这个(http://css4-selectors.com/browser-selector-test/)可以测试浏览器的支持情况。
选择一个元素
在一个网页中选择单独某个元素来设置可能会非常的普遍,这部分将教你如何使用d3选择器选取单个元素。
准备好了吗,在你的编辑器中输入如下代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Single Selection</title>
<link rel="stylesheet" type="text/css" href="https://github.com/NickQiZhu/d3-cookbook/blob/master/css/styles.css"/>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<p id="target"></p> <!-- A -->
<script type="text/javascript">
d3.select("p#target") // <-- B
.text("Hello world!"); // <-- C
</script>
</body>
</html>
(d3.select()是d3选择器的特点用法,和jquery的$符,还有document.getElement的用法和原理一样)
打开浏览器运行:你会看到你的屏幕中打出了经典的“Hello world!”。
现在让我们看看他究竟是如何工作的,首先我们在body中写入了id=“target”的p元素,在js语句中,我们通过d3选择器选择到了该元素,这里并不算什么,它的强大之处在于我们可以直接在后面对选择到的元素链接操作,在本例中就将Hello world的字符串放入了该元素中,于是就在浏览器中显示了出来。不光是可以设置它的文字,还可以设置颜色,背景,等属性,甚至是onclick等事件都可以直接通过“.”号链接到该元素上。
.text方法我们已经见识到了,就是设置他的文本信息,下面介绍几种其他常用的方法。
.attr 方法,直接举个例子:d3.select("p").attr("foo","goo");//设置p元素的foo属性的值为goo。
d3.select("p").attr("foo");//获取p元素的foo属性的值。
.classed 方法,该方法可以让你对元素的类进行操作,比如:
d3.select("p").classed("goo");//判断p元素是否拥有类goo的属性。
d3.select("p").classed("goo",ture);//为p元素添加类goo。
//参数的值同样可以通过函数来返回
d3.select("p").classed("goo",function(){return false;});//删除p元素的类goo;
.style 方法,可以让你设置元素的样式,比如:
d3. select( "p"). style( "font-size");//获取p元素的font-size样式。
d3. select( "p"). style( "font-size","10px");//设置p元素的font-size样式文字大小为10px。
//同样这里也可以使用函数动态生成返回值
d3. select( "p"). style( "font-size",function(){return normalFontSize+10; });
.text方法, 可以让你对元素内部的文本内容进行操作,比如:
d3. select( "p"). text();// 获取p元素中的文本信息。
d3. select( "p"). text("hello");//设置p元素的文本信息为”hello“。
//同样这里也可以使用函数
d3. select( "p"). text(function(){ var model = retrieveModel();
return model.message; });
.html 方法,操作元素的innerhtml。比如:
d3. select( "p"). html();// 获取p元素中的innerhtml。
d3. select( "p"). html("<b>hello</b>");// 设置p元素中的innerhtml。
这些方法可以用在选取的单个元素或者多个元素上,当用在多个元素上时,会自动的对每个元素都增加相应的操作。后面我们会学到更多更复杂的应用。
选择多个元素
有时候我们不光要选择某个元素,可能需要选择某组元素,就是选择多个元素。下面来看看d3是如何选取多个元素的。
先来看下面的代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Multiple Selection</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<style>
.red.box{width:200px; height:200px; background:red; margin:5px;}
</style>
</head>
<body>
<div></div>
<div></div>
<div></div>
<script type="text/javascript">
d3.selectAll("div") // <-- A
.attr("class", "red box"); // <-- B
</script>
</body>
</html>
浏览器打开该程序后,你会看到3个红色的div,这里注意我们用到和前面不一样的d3.selectAll(),前面的d3.select(),如果有多个的话,选择中的是页面中的第一个,二这里的d3.selectAll(),选择的是页面中所有的div,在body中我们看到有3个div并且没有任何属性,但是用浏览器打开后,如果你的浏览器有审查元素这个工具的话你会看到:
3个div都被增加了一个red box类的属性,那么这个代码应该很容易理解了,A处的代码选择了页面中所有的div元素,然后attr方法将他们每个元素都设置了class属性为red box,于是我们就在页面中看到了3个红色的div。
你可能会发现这种语法和前面单个选择的时候几乎是一模一样,没错,d3选择器就是这样,不管是选择单个还是多个都可以用同样的语法,而且所选的元素都会被遍历执行到,你会发现不管这例子中有多少个div只要同样的A,B语句就可以完成给他们全部都设置同样的class,现在也许你并没有发现这的特别之处,但是在后面的数据可视化中你就会发现他的好处了。
循环遍历一个选择器
有的时候我们可能需要在一个选择中,循环遍历每个元素,并且根据他们在一个选择集合中的位置为每个元素设置属性,在这一部分将告诉你如何用d3来实现这一功能。
还是先看实例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Multiple Selection</title>
<link rel="stylesheet" type="text/css" href="https://github.com/NickQiZhu/d3-cookbook/blob/master/css/styles.css"/>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<style>
.red.box{width:200px; height:200px; background: #C30; margin:5px;}
</style>
</head>
<body>
<div></div>
<div></div>
<div></div>
<script type="text/javascript">
d3.selectAll("div") // <-- A
.attr("class", "red box") // <-- B
.each(function (d, i) { // <-- C
d3.select(this).append("h1").text(i); // <-- D
});
</script>
</body>
</html>
浏览器运行以上代码我们会看到:
通过审查元素我们可以看到,3个div都被赋予了class为red box的属性,并且每个div里面都插入了一个<h1>标签,标签里面又分别设置了0,1,2。下面就来解释下这是如何发生的,那么这个例子前面的部分和前一个例子一个,我们在A处选择了页面中所有的div元素,在B处为每个div设置class属性,在C处用到了一个新的方法each。我们用each方法循环遍历选中的一组元素中的每一个,并且为每一个设置不同的属性,这里each(function(d,i){})我们叫它迭代函数,function(d,i)有两个参数,d代表的是当前被调用的dom元素,i代表当前元素在整个元素集合中的索引号,或者说元素数组更容易理解,数组的索引是从0开始的,所有我们看到第一个元素i的值为0。
我们看到D行又有一个新的方法append(""),这个方法是在当前所选中的元素中(本例Div)增加一个传入的参数名的标签(本例h1)作为最最末尾的孩子节点(标签)。并且返回一个当前插入的元素(本例中的h1),这时我们就能理解后面的.text的作用了,就是设置这个新插入的h1标签的内容为“i”即当前div元素所处整个div元素的索引。这部分可能有点绕。能力有限,也许翻译的不够好,但是我相信多看几遍还是能明白的。最后梳理一般整个流程应该是这样的。
A:选择页面中所有的div元素,此时得到的是一个有3个div元素的数组元素。
B :给这个数组中的每个div都设置class属性。
C : 调用迭代函数
D : 这里有一个d3.select(this),这个就表示当前所选中的元素,因为这个each函数是一个一个执行的,有多少个元素,它就执行多少遍,第一次是选中的所有div中的第一个,这个this就表示执行时当前选中的,然后为这个选择中的div增加一个子标签(h1),append()在执行增加时同时返回插入的元素即h1,这时再通过.text(i)设置每个div中h1的文本信息。这里需要注意的是i是索引值从0开始。
提示: DOM元素有非常丰富的接口,如果你感兴趣可以访问DOM元素的API(https://developer.mozilla.org/en-US/docs/DOM/element)
4.子(部分)选择器
在某些时候我们肯能需要某个选择集合中的某些元素,比如某个集合中的所有div,或者某个div中的所有p元素等,这时我们该如何使用d3呢。
还是直接看例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Multiple Selection</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<style>
.red.box{width:200px; height:200px; background: #C30; margin:5px;}
.blue.box{width:200px; height:200px; background: #3CC; margin:5px;}
</style>
</head>
<body>
<section id="section1">
<div>
<p>blue box</p>
</div>
</section>
<section id="section2">
<div>
<p>red box</p>
</div>
</section>
<script type="text/javascript">
d3.select("#section1 > div") // <-- A
.attr("class", "blue box");
d3.select("#section2") // <-- B
.select("div") // <-- C
.attr("class", "red box");
</script>
</body>
</html>
用浏览器运行上面的代码你会看到:
有2个不同颜色的div,这里用2种不同的方式选择了一个集合中的div,A处的代码就是选择id为section1元素里面的div元素,B处同样也是,首先选择id为section2的元素然后选择该元素下面的div。然后就是前面讲到的为其添加class属性。
那么这两种方式有什么区别呢,在这之前我们先看看之前讲讲descendant combinator,这种方式我们称他为子孙组合选择,他的语法就是像这样selector selector。那么这种组合选择适合那种比较宽松的父子结构,为什么说宽松呢,因为这里我们并不关心第二个selector是第一个的直接子标签或者是孙子标签甚至更深的子标签,只要第二个selector包含在第一个里面就可以,比如:
<div><span>it is<em>red</em></span></div>
我们使用 div em , 就可以选中其中的em元素,即使他不是div的直接子标签。
下面我们说到了A处用到的Child combinator,叫父子组合选择吧,这种选择器的语法规则为selector>selector,父子组合给选择器更小的约束就是只选择第一个元素下的直接子元素。
这时候如果这段代码
<div><span>it is<em>red</em></span></div>
要选中em元素就要使用 span>em。如果使用div>em这时就选取不到任何元素了,因为em不是div的直接子标签。
小提示:以上2种方式都属于css3标准,css3标准还支持兄弟组合的选择器如果你感兴趣可以浏览(http://www.w3.org/TR/css3-selectors/#sibling-combinators)
在B和C处,使用了不同风格和技术的子选择器,在B处我们通过普通的d3选择器选取了id为section2的元素,紧接着链接了一个新的选择器,选取了div元素,这个在语法和逻辑上都比较容易理解,就是选择在id为section2元素下的div元素,也许这样看来和前面的方式没什么不同,但是这种方法允许你在选取子元素之前,对父元素进行操作,
d3. select("# section2") // <-- B
.style( "font-size", "2em") // <-- B-1
.select( "div") // <-- C
.attr( "class", "red box");
看上面的代码,在选择div之前通过B-1处可以设置id为section2的元素的样式。这种灵活性在以后的章节中会进一步体现。
5.方法链条
正如你所看到的,d3的设计的一个核心思想就是方法链条,因此他几乎是从一个领域特化语言(Domain Specific Language)来动态的生成一个html或者svg。在下面的例子中我们将会看到,d3是如何来动态的设置网页的结构并修改其样式和属性的。领域特化语言是一个新概念,如果有问题可以Google或者百度,这个我也不是很清楚
这本书的作者推荐的地址(http://www.informit.com/articles/article.aspx?p=1592379),有兴趣的可以看看。
下面来看代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Selection Function Chain</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<style>
.red.box{width:200px; height:200px; background: #C30; margin:5px;}
.blue.box{width:200px; height:200px; background: #3CC; margin:5px;}
</style>
</head>
<body>
<script type="text/javascript">
var body = d3.select("body"); // <-- A
body.append("section") // <-- B
.attr("id", "section1") // <-- C
.append("div") // <-- D
.attr("class", "blue box") // <-- E
.append("p") // <-- F
.text("dynamic blue box"); // <-- G
body.append("section")
.attr("id", "section2")
.append("div")
.attr("class", "red box")
.append("p")
.text("dynamic red box");
</script>
</body>
</html>
上面的代码中body里面没有任何元素,但是通过浏览器运行后我们会发现:
这时网页的结构和前一个例子一样。尽管效果几乎一样但代码却截然不同,在下面的例子中,body中并没有任何的静态元素。我们来看看是如何动态生成的,在代码A行,我们通过d3选择器选取了页面中的body元素并将该元素赋值给一个变量body。接着在B处body里面被增加了一个新的标签section,记住append返回的是插入的元素,在append所以这里实际上是选择到了新插入的section,在C处通过attr方法,给这个secton设置id属性。然后继续调用append方法在D处,在section标签内插入div同时返回这个div。然后在E处,又通过attr方法给div设置class属性,接着又调用了一个append在div中再插入p元素,然后用text方法设置p元素的文本信息。然后用同样的方式又创建了一个section。于是就动态生成了和前面的例子一样结果的网页。
以这个例子来说明,这种方法链条结构可以继续创造出更多更复杂的构架,事实上,这就是典型的d3数据可视化构架的创建方式,许多数据可视化通常只包含简单的html框架,其他的就通过d3来动态的创建。如果你想熟练的运用d3就必须了解和掌握这种风格。