入门指南

D3.js 的函数风格,使得各种组件之间的代码重用成为现实。

如果使用函数式风格创建对象,并且该对象所有的方法都没有用 this,那这个对象就是持久的。持久对象就是对许多功能行为的集合。

选集

虽然选择器 API 有助于在文档中选择元素,然而为了操作这些元素,读者需要遍历每个元素。为了减少开发中的琐碎工作,D3 引入了自己的选择器 API。

选取单个元素

d3.select() 中选择器得到的是多个元素,函数只返回第一个元素。

  1. <p id="target"></p> <!-- A -->
  2. <script type="text/javascript">
  3. d3.select("p#target") // <-- B
  4. .text("Hello world!") // <-- C
  5. .attr("data-id", "1")
  6. .style("color", "red")
  7. .classed("goo",function () {
  8. return ! d3.select(this).classed("goo")
  9. });
  10. </script>

这些修饰函数可以作用于单个以及多个元素,当应用于多个元素选集时,这些函数会依次作用于每个元素。

选取多个元素

  1. <div></div>
  2. <div></div>
  3. <div></div>
  4. <script type="text/javascript">
  5. d3.selectAll("div")
  6. .attr("class", "red box");
  7. </script>

迭代选集中的元素

  1. <div></div>
  2. <div></div>
  3. <div></div>
  4. <script type="text/javascript">
  5. d3.selectAll("div")
  6. .attr("class", "red box")
  7. .each(function (d, i) {
  8. d3.select(this).append("h1").text(i);
  9. });
  10. </script>

使用子选择器

D3 提供两种方式选择子元素

  1. <section id="section1">
  2. <div>
  3. <p>blue box</p>
  4. </div>
  5. </section>
  6. <section id="section2">
  7. <div>
  8. <p>red box</p>
  9. </div>
  10. </section>
  11. <script type="text/javascript">
  12. d3.select("#section1 > div")
  13. .attr("class", "blue box");
  14. d3.select("#section2")
  15. .style("font-size", "2em")
  16. .select("div")
  17. .attr("class", "red box");
  18. </script>

处理原始选集

  1. <table class="table">
  2. <thead>
  3. <tr>
  4. <th>Time</th>
  5. <th>Type</th>
  6. <th>Amount</th>
  7. </tr>
  8. </thead>
  9. <tbody>
  10. <tr>
  11. <td>10:22</td>
  12. <td>Purchase</td>
  13. <td>$10.00</td>
  14. </tr>
  15. <tr>
  16. <td>12:12</td>
  17. <td>Purchase</td>
  18. <td>$12.50</td>
  19. </tr>
  20. <tr>
  21. <td>14:11</td>
  22. <td>Expense</td>
  23. <td>$9.70</td>
  24. </tr>
  25. </tbody>
  26. </table>
  27. <script type="text/javascript">
  28. var trSelection = d3.selectAll("tr");
  29. var headerElement = trSelection.nodes()[0];
  30. console.log("headerElement is an instance of DOM Element: "
  31. + (headerElement instanceof Element));
  32. d3.select(headerElement).attr("class", "table-header");
  33. console.log("d3.select(headerElement) is an instanceof of d3.selection: "
  34. + (d3.select(headerElement) instanceof d3.selection));
  35. var rows = trSelection.nodes();
  36. d3.select(rows[1]).attr("class", "table-row-odd");
  37. d3.select(rows[2]).attr("class", "table-row-even");
  38. d3.select(rows[3]).attr("class", "table-row-odd");
  39. </script>

数据处理

进入-退出-更新模式

数据绑定的时候可能出现 DOM 元素与数据元素个数不匹配的问题,那么 enterexit 就是用来处理这个问题的。enter 操作用来添加新的 DOM 元素,exit 操作用来移除多余的 DOM 元素。

如果数据元素多于 DOM 个数时用 enter,如果数据元素少于 DOM元素,则用 exit

在数据绑定时候存在三种情形:

  • 数据元素个数多于 DOM 元素个数
  • 数据元素与 DOM 元素个数一样
  • 数据元素个数少于 DOM 元素个数

将数组绑定为数据

  1. var data = [10, 15, 30, 50, 80, 65, 55, 30, 20, 10, 8];
  2. function render(data) {
  3. var bars = d3.select("body")
  4. .selectAll("div.h-bar")
  5. .data(data); // Update
  6. // Enter
  7. bars.enter()
  8. .append("div")
  9. .attr("class", "h-bar")
  10. .merge(bars) // Enter + Update
  11. .style("width", function (d) {
  12. return (d * 3) + "px";
  13. })
  14. .text(function (d) {
  15. return d;
  16. });
  17. // Exit
  18. bars.exit()
  19. .remove();
  20. }
  21. setInterval(function () {
  22. data.shift();
  23. data.push(Math.round(Math.random() * 100));
  24. render(data);
  25. }, 800);
  26. render(data);

效果如下图:

1.gif

D3 会在相应的 DOM 元素中添加一个名为 __data__ 的属性,通过这个属性将数据和图形联系起来。

将函数绑定为数据

  1. var data = [];
  2. var datum = function (x) {
  3. return 15 + x * x;
  4. };
  5. var newData = function () {
  6. data.push(datum);
  7. return data;
  8. };
  9. function render(){
  10. var divs = d3.select("#container")
  11. .selectAll("div")
  12. .data(newData);
  13. divs.enter().append("div").append("span");
  14. divs.attr("class", "v-bar")
  15. .style("height", function (d, i) {
  16. return d(i) + "px";
  17. })
  18. .select("span")
  19. .text(function(d, i){
  20. return d(i);
  21. });
  22. divs.exit().remove();
  23. }
  24. setInterval(function () {
  25. render();
  26. }, 1000);
  27. render();

数组的过滤

  1. <script type="text/javascript">
  2. var data = [
  3. {expense: 10, category: "Retail"},
  4. {expense: 15, category: "Gas"},
  5. {expense: 30, category: "Retail"},
  6. {expense: 50, category: "Dining"},
  7. {expense: 80, category: "Gas"},
  8. {expense: 65, category: "Retail"},
  9. {expense: 55, category: "Gas"},
  10. {expense: 30, category: "Dining"},
  11. {expense: 20, category: "Retail"},
  12. {expense: 10, category: "Dining"},
  13. {expense: 8, category: "Gas"}
  14. ];
  15. function render(data, category) {
  16. var bars = d3.select("body").selectAll("div.h-bar")
  17. .data(data);
  18. // Enter
  19. bars.enter()
  20. .append("div")
  21. .attr("class", "h-bar")
  22. .style("width", function (d) {
  23. return (d.expense * 5) + "px";}
  24. )
  25. .append("span")
  26. .text(function (d) {
  27. return d.category;
  28. });
  29. // Update
  30. d3.selectAll("div.h-bar").attr("class", "h-bar");
  31. // Filter
  32. bars.filter(function (d, i) {
  33. return d.category == category;
  34. })
  35. .classed("selected", true);
  36. }
  37. render(data);
  38. function select(category) {
  39. render(data, category);
  40. }
  41. </script>
  42. <div class="control-group">
  43. <button onclick="select('Retail')">
  44. Retail
  45. </button>
  46. <button onclick="select('Gas')">
  47. Gas
  48. </button>
  49. <button onclick="select('Dining')">
  50. Dining
  51. </button>
  52. <button onclick="select()">
  53. Clear
  54. </button>
  55. </div>

基于数据的图形排序

  1. <script type="text/javascript">
  2. var data = [
  3. {expense: 10, category: "Retail"},
  4. {expense: 15, category: "Gas"},
  5. {expense: 30, category: "Retail"},
  6. {expense: 50, category: "Dining"},
  7. {expense: 80, category: "Gas"},
  8. {expense: 65, category: "Retail"},
  9. {expense: 55, category: "Gas"},
  10. {expense: 30, category: "Dining"},
  11. {expense: 20, category: "Retail"},
  12. {expense: 10, category: "Dining"},
  13. {expense: 8, category: "Gas"}
  14. ];
  15. function render(data, comparator) {
  16. var bars = d3.select("body").selectAll("div.h-bar")
  17. .data(data);
  18. // Enter
  19. bars.enter().append("div")
  20. .attr("class", "h-bar")
  21. .append("span");
  22. // Update
  23. d3.selectAll("div.h-bar")
  24. .style("width", function (d) {
  25. return (d.expense * 5) + "px";
  26. })
  27. .select("span")
  28. .text(function (d) {
  29. return d.category;
  30. });
  31. // Sort
  32. if(comparator)
  33. bars.sort(comparator);
  34. }
  35. var compareByExpense = function (a, b) {
  36. return a.expense < b.expense?-1:1;
  37. };
  38. var compareByCategory = function (a, b) {
  39. return a.category < b.category?-1:1;
  40. };
  41. render(data);
  42. function sort(comparator) {
  43. render(data, comparator);
  44. }
  45. </script>
  46. <div class="control-group">
  47. <button onclick="sort(compareByExpense)">
  48. Sort by Expense
  49. </button>
  50. <button onclick="sort(compareByCategory)">
  51. Sort by Category
  52. </button>
  53. <button onclick="sort()">
  54. Reset
  55. </button>
  56. </div>

从服务器加载数据

  1. <div id="chart"></div>
  2. <script type="text/javascript">
  3. function render(data) {
  4. var bars = d3.select("#chart").selectAll("div.h-bar")
  5. .data(data);
  6. bars.enter().append("div")
  7. .attr("class", "h-bar")
  8. .style("width", function (d) {
  9. return (d.expense * 5) + "px";
  10. })
  11. .append("span")
  12. .text(function (d) {
  13. return d.category;
  14. });
  15. }
  16. function load(){
  17. d3.json("data.json", function(error, json){
  18. render(json);
  19. });
  20. }
  21. </script>
  22. <div class="control-group">
  23. <button onclick="load()">Load Data from JSON feed</button>
  24. </div>

利用队列异步加载数据

  1. <div id="chart"></div>
  2. <script type="text/javascript">
  3. function render(data) {
  4. var bars = d3.select("#chart").selectAll("div.h-bar")
  5. .data(data);
  6. bars.enter().append("div")
  7. .attr("class", "h-bar")
  8. .style("width", function (d) {
  9. return (d.number) + "px";
  10. })
  11. .append("span")
  12. .text(function (d) {
  13. return d.number;
  14. });
  15. }
  16. function generateDatum(callback) {
  17. setInterval(function(){
  18. callback(null, {number: Math.ceil(Math.random() * 500)});
  19. }, 500);
  20. }
  21. function load() {
  22. var q = d3.queue();
  23. for (var i = 0; i < 10; i++)
  24. q.defer(generateDatum);
  25. q.awaitAll(function (error, data) {
  26. render(data);
  27. });
  28. }
  29. </script>
  30. <div class="control-group">
  31. <button onclick="load()">Generate Data Set</button>
  32. </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 中文文档