前端编程提高之旅(八)—-D3.js数据可视化data join解析

最后更新于:2022-04-01 09:37:05

**[D3.js](http://d3js.org/)作为一门轻型的可视化类库,非常便于将数据与web界面元素绑定,实现可视化。乐帝d3.js入门是大体看了一遍[《d3js数据可视化实战》](http://download.csdn.net/detail/loganyang123/6781653)这本书,D3操作非常类似于jquery的使用,具体体现在两点:** - **选择器模块都采用CSS3标准** - **方法可以链式调用** **有了jquery使用基础,相信再加上以上书籍的例子,调试很容易上手使用D3.js,乐帝目前认为D3.js与jquery区别在于:D3.js独有的数据结构概念及对SVG操作方便的实现。而深入理解D3原理,以上皮毛的理解就不够用了。** **通过阅读上述书籍乐帝将D3内容划了几大块来分开理解:** **DOM操作包括:** - **选择器模块(select、selectAll等方法)及如何实现链式调用。** - **节点模块(append、remove等方法实现)** - **样式模块(attr、style等方法实现)** **数据绑定相关方法:** - **data方法** - **enter、exit等方法** **比例尺:** - **值域与输出域的实现** **更新、过渡、动画方法:** - **transition方法及连带duration、ease、delay等方法** **事件:** - **即绑定事件** **而后通读[API](https://github.com/mbostock/d3/wiki/API-Reference)发现对于理解D3.js实现机理有一些关键概念,这里关键概念涉及selection、data join、group、transition等。而乐帝读API最大心得在于,在没有一个大体概念时,千万不要去触碰源码,还是按部就班读API吧,读不懂把文档翻译一遍就懂了,乐帝是这么做的。** **这篇文章乐帝主要想讨论[data join](http://download.csdn.net/detail/yingyiledi/8074405),但在讨论之前,还是需要补充下基本概念。** **D3.js独有关键概念是selection,乐帝将其翻译成元素集。它表示从当前文档获取的元素数组。它定义了一种数据结构,或者说是对原有dom文档数据结构的修改。有了元素集之后,就可以对元素执行常规操作了,诸如属性、样式、参数、文档内容等。** **selection存在的意义在于,是它将页面元素与数据实现绑定连接,连接的数据又可以产生enter和exit子元素集,因此能够反映数据的变化,用于添加或移除元素。** **D3支持方法链,操作方法返回值是元素集,从这一句话,我们知道不管如何具体实现链式操作,我们知道返回的是元素集就够了。** **乐帝最近思考,整个web世界甚至整个世界,由两种力量驱动:数据与人。而某些高深思想或许能把人也归结为数据。D3是这两种力量的典型,数据驱动展现,人驱动交互。脱离人与数据,D3的代码,只是一堆分离的函数而已。D3紧紧拥抱数据,这就是其最大特点。** **selection介绍完了,我们开始介绍data join概念。** **由上所述,D3是数据驱动,而元素集是沟通数据与元素的集合,而data join的概念又是代表数据与元素结合三种状态的概念,由此看来,data join在D3中属于核心概念。** **[《以data join概念思考》](http://bost.ocks.org/mike/join/)这篇文章给出了对data join比较简明易懂的陈述。** **在D3中任何时候,数据与页面元素都有三种关系:数据与元素绑定时,即一一对应,这样构成的selection状态叫update selection;没有元素与之对应的数据构成的selection叫enter selection;没有数据与之对应的元素构成的selection叫exit selection,它代表将要被移除的元素集。** **如下图:** **![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd5e43c8327.jpg)** **从字面上理解,D3对数据的推崇可谓毫无节操,有数据没元素叫enter,有元素没数据叫exit,代表要被踢出去的元素。** **下面来看一段代码例子:** ~~~ svg.selectAll("circle") .data(data) .enter().append("circle") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .attr("r", 2.5); ~~~ **首先来看第一句svg.selectAll("circle"),它返回空的元素集,因为SVG容器是空的。** **** **![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd5e43d6e4b.jpg)** **然后上述空元素集与数据结合,构成新的元素集,包含enter、update、exit三种状态。因为元素集是空的,所以update和exit元素集为空,而enter状态的元素集,则包括五个占位符元素。** ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd5e43e56d1.jpg) selection.enter后,返回enter元素集,此时为五个绑定数据的对象。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd5e43f1bb8.jpg) **最后append("circle")一步,使得enter元素集实现与元素一一对应,没错构造成了上述的update元素集状态。** **由上述例子不难看出,给页面对象添加子对象不用for循环,而是采取data join的概念,用意在于,在静态展现的基础上,对update及exit做微小改动,就可以使它实现动态展现。这就意味着你可以看实时数据,允许数据集合的交互行为及温和过渡效果展现。** **任何时候运行代码,都会重新计算data join,从而保证数据与元素预期的关系。** **data join允许我们队指定状态进行操作,比如,可是设置常数值在enter上,而不是update上,通过重新选择元素最小化改变原有dom,大大提升渲染效率。** **下面乐帝展示一个[对各个状态data join操作](http://bl.ocks.org/mbostock/5779690#index.html)的例子:** **起始HTML:** ~~~ <div>update</div> <div>exit</div> ~~~ **起始D3代码:** ~~~ var dataset =["enter", "hello"]; var key = function(d) { return d || this.textContent; } var duration = 750; var div = d3.select("body").selectAll("div") .data(dataset,key); ~~~ **此时data join 三个状态:** **![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd5e440ec8a.jpg)** **![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd5e4424d05.jpg)** **如上图,不难分析得到,此时data join三状态:update元素集为空,enter元素集已经有数据绑定,exit元素集有两个div元素,在第二张图中innerHTML属性中,发现恰恰是初始化HTML的两个div元素,这里采用了键函数(key)方法,键函数被调用了四次,前两次调用的是已有的div数据调用,后两次则是enter状态元素集数据调用。** **接下来对exit元素集操作:** ~~~ // // 1. exit var exitTransition = d3.transition().duration(750).each(function() { div.exit() .style("background", "red") .transition() .style("opacity", 0) .remove();//移除节点 }); ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd5e4435dfe.jpg) **不难得出结论,是原有两个div将要被移除,在页面及内存中清除。** **接下来对enter的操作,这里update为空,故第二步update操作并没有实际意义。** ~~~ // 2. update var updateTransition = exitTransition.transition().each(function() { div.transition() .style("background", "orange"); }); // 3. enter var enterTransition = updateTransition.transition().each(function() { div.enter().append("div") .text(function(d) { return d; }) .style("opacity", 0) .transition() .style("background", "green") .style("opacity", 1); }); ~~~ **![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd5e448b9b6.jpg)** **![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd5e449b9bb.jpg)** **第三步对enter元素集的操作,使其与元素绑定,并设置成绿色。** **乐帝在调试期间还发现了另外的情况:** **一开始js代码是这样的:** ~~~ var dataset =["enter", "update"]; var key = function(d) { return d || this.textContent; } var duration = 750; var div = d3.select("body").selectAll("div") .data(dataset,key); ~~~ **即dataset有一个元素与已存在的div文本内容相同,又由于键函数是处理的是dataset与div文本内容的集合,经过运行如下:** **![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd5e44a8f29.jpg)** **与之上比较不难发现,这里update元素集有一个元素,enter元素集有一个enter状态元素。另看exit元素集:** **![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd5e44b8116.jpg)** **exit及enter都少了一个对应状态的对象,这里乐帝猜测d3应该是将每个键值函数返回数据进行了查重操作,将两个“update”文本数据合并成一个update状态元素了。** **执行1.exit代码,此时只有exit文本的div被移除。执行2.update代码时,update文本的div被设置为背景为橘黄色。最后执行3.enter代码时,背景色为绿色的div被加入到文档。**
';