入门指南
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
// Enter
bars.enter()
.append("div")
.attr("class", "h-bar")
.merge(bars) // Enter + Update
.style("width", function (d) {
return (d * 3) + "px";
})
.text(function (d) {
return d;
});
// Exit
bars.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);
// Enter
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;
});
// Update
d3.selectAll("div.h-bar").attr("class", "h-bar");
// Filter
bars.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);
// Enter
bars.enter().append("div")
.attr("class", "h-bar")
.append("span");
// Update
d3.selectAll("div.h-bar")
.style("width", function (d) {
return (d.expense * 5) + "px";
})
.select("span")
.text(function (d) {
return d.category;
});
// Sort
if(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 中文文档