入门指南
D3.js 的函数风格,使得各种组件之间的代码重用成为现实。
如果使用函数式风格创建对象,并且该对象所有的方法都没有用 this,那这个对象就是持久的。持久对象就是对许多功能行为的集合。
选集
虽然选择器 API 有助于在文档中选择元素,然而为了操作这些元素,读者需要遍历每个元素。为了减少开发中的琐碎工作,D3 引入了自己的选择器 API。
选取单个元素
若 d3.select() 中选择器得到的是多个元素,函数只返回第一个元素。
<p id="target"></p> <!-- A --><script type="text/javascript">d3.select("p#target") // <-- B.text("Hello world!") // <-- C.attr("data-id", "1").style("color", "red").classed("goo",function () {return ! d3.select(this).classed("goo")});</script>
这些修饰函数可以作用于单个以及多个元素,当应用于多个元素选集时,这些函数会依次作用于每个元素。
选取多个元素
<div></div><div></div><div></div><script type="text/javascript">d3.selectAll("div").attr("class", "red box");</script>
迭代选集中的元素
<div></div><div></div><div></div><script type="text/javascript">d3.selectAll("div").attr("class", "red box").each(function (d, i) {d3.select(this).append("h1").text(i);});</script>
使用子选择器
D3 提供两种方式选择子元素
<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").attr("class", "blue box");d3.select("#section2").style("font-size", "2em").select("div").attr("class", "red box");</script>
处理原始选集
<table class="table"><thead><tr><th>Time</th><th>Type</th><th>Amount</th></tr></thead><tbody><tr><td>10:22</td><td>Purchase</td><td>$10.00</td></tr><tr><td>12:12</td><td>Purchase</td><td>$12.50</td></tr><tr><td>14:11</td><td>Expense</td><td>$9.70</td></tr></tbody></table><script type="text/javascript">var trSelection = d3.selectAll("tr");var headerElement = trSelection.nodes()[0];console.log("headerElement is an instance of DOM Element: "+ (headerElement instanceof Element));d3.select(headerElement).attr("class", "table-header");console.log("d3.select(headerElement) is an instanceof of d3.selection: "+ (d3.select(headerElement) instanceof d3.selection));var rows = trSelection.nodes();d3.select(rows[1]).attr("class", "table-row-odd");d3.select(rows[2]).attr("class", "table-row-even");d3.select(rows[3]).attr("class", "table-row-odd");</script>
数据处理
进入-退出-更新模式
数据绑定的时候可能出现 DOM 元素与数据元素个数不匹配的问题,那么 enter 和 exit 就是用来处理这个问题的。enter 操作用来添加新的 DOM 元素,exit 操作用来移除多余的 DOM 元素。
如果数据元素多于 DOM 个数时用 enter,如果数据元素少于 DOM元素,则用 exit。
在数据绑定时候存在三种情形:
- 数据元素个数多于
DOM元素个数 - 数据元素与
DOM元素个数一样 - 数据元素个数少于
DOM元素个数
将数组绑定为数据
var data = [10, 15, 30, 50, 80, 65, 55, 30, 20, 10, 8];function render(data) {var bars = d3.select("body").selectAll("div.h-bar").data(data); // Update// Enterbars.enter().append("div").attr("class", "h-bar").merge(bars) // Enter + Update.style("width", function (d) {return (d * 3) + "px";}).text(function (d) {return d;});// Exitbars.exit().remove();}setInterval(function () {data.shift();data.push(Math.round(Math.random() * 100));render(data);}, 800);render(data);
效果如下图:

D3 会在相应的 DOM 元素中添加一个名为 __data__ 的属性,通过这个属性将数据和图形联系起来。
将函数绑定为数据
var data = [];var datum = function (x) {return 15 + x * x;};var newData = function () {data.push(datum);return data;};function render(){var divs = d3.select("#container").selectAll("div").data(newData);divs.enter().append("div").append("span");divs.attr("class", "v-bar").style("height", function (d, i) {return d(i) + "px";}).select("span").text(function(d, i){return d(i);});divs.exit().remove();}setInterval(function () {render();}, 1000);render();
数组的过滤
<script type="text/javascript">var data = [{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) {var bars = d3.select("body").selectAll("div.h-bar").data(data);// Enterbars.enter().append("div").attr("class", "h-bar").style("width", function (d) {return (d.expense * 5) + "px";}).append("span").text(function (d) {return d.category;});// Updated3.selectAll("div.h-bar").attr("class", "h-bar");// Filterbars.filter(function (d, i) {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>
基于数据的图形排序
<script type="text/javascript">var data = [{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) {var bars = d3.select("body").selectAll("div.h-bar").data(data);// Enterbars.enter().append("div").attr("class", "h-bar").append("span");// Updated3.selectAll("div.h-bar").style("width", function (d) {return (d.expense * 5) + "px";}).select("span").text(function (d) {return d.category;});// Sortif(comparator)bars.sort(comparator);}var compareByExpense = function (a, b) {return a.expense < b.expense?-1:1;};var compareByCategory = function (a, b) {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 Expense</button><button onclick="sort(compareByCategory)">Sort by Category</button><button onclick="sort()">Reset</button></div>
从服务器加载数据
<div id="chart"></div><script type="text/javascript">function render(data) {var bars = d3.select("#chart").selectAll("div.h-bar").data(data);bars.enter().append("div").attr("class", "h-bar").style("width", function (d) {return (d.expense * 5) + "px";}).append("span").text(function (d) {return d.category;});}function load(){d3.json("data.json", function(error, json){render(json);});}</script><div class="control-group"><button onclick="load()">Load Data from JSON feed</button></div>
利用队列异步加载数据
<div id="chart"></div><script type="text/javascript">function render(data) {var bars = d3.select("#chart").selectAll("div.h-bar").data(data);bars.enter().append("div").attr("class", "h-bar").style("width", function (d) {return (d.number) + "px";}).append("span").text(function (d) {return d.number;});}function generateDatum(callback) {setInterval(function(){callback(null, {number: Math.ceil(Math.random() * 500)});}, 500);}function load() {var q = d3.queue();for (var i = 0; i < 10; i++)q.defer(generateDatum);q.awaitAll(function (error, data) {render(data);});}</script><div class="control-group"><button onclick="load()">Generate Data Set</button></div>
参考
【1】Data Visualization and D3.js(视频)
【2】D3 4.x 数据可视化实战手册@[加]朱启
【3】https://github.com/NickQiZhu/d3-cookbook-v2(D3 4.x 数据可视化实战手册 代码)
【4】D3.js 中文文档
