d3.js学习笔记--Mike Bostock: VIZBI: D3 Workshop

一个人想着一个人 提交于 2019-12-06 17:05:04

Preface

    d3是基于HTML和SVG的数据可视化JS库. 它将数据(data)和元素(DOM)相互绑定在一起, 并且在数据实时改变情况下DOM元素也会实时改变.

所以, D3是Data-Driven Documents, 即数据驱动元素, 而D3的命名则来源于W3C Document Object Model.

html模板文件为:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>

<script src="d3.js"></script>
<script>

</script>

我们需要在当前html模板文件下启动一个简单的服务器, 这样才能读取数据文件:

leicj@lishunan:~$ python -m SimpleHTTPServer 8888 &
[1] 16960
leicj@lishunan:~$ Serving HTTP on 0.0.0.0 port 8888 ...
127.0.0.1 - - [23/Oct/2016 13:02:13] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [23/Oct/2016 13:02:13] code 404, message File not found
127.0.0.1 - - [23/Oct/2016 13:02:13] "GET /favicon.ico HTTP/1.1" 404 -
127.0.0.1 - - [23/Oct/2016 13:02:37] "GET /test/ HTTP/1.1" 200 -
127.0.0.1 - - [23/Oct/2016 13:02:37] "GET /test/d3.js HTTP/1.1" 200 -

 

Selections

d3的选择器和jQuery很像, 以下是经常要用到的选择器:

#foo // <any id="foo">
foo // <foo>
.foo // <any class="foo">
[foo=bar] // <any foo="bar">
foo bar // <foo><bar></foo>
foo.bar // <foo class="bar">
foo#bar // <foo id="bar">

d3使用selectAll来筛选, 所以:

d3.selectAll("pre,code")

等价于jQuery中:

$("pre,code")

选择器本身返回的是一个数组. 

一个实际的例子:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  svg {
    margin-top: 20px;
    margin-left: 20px;
  }
</style>

<svg height="960" width="960">
  <circle></circle>
</svg>
<script src="d3.js"></script>
<script>
  var circle = d3.selectAll("circle");
  circle.attr('cx', 50);
  circle.attr('cy', 52);
  circle.attr('r', 24);
  circle.style("fill", "red");

</script>

绘制一个circle:

而d3支持链式编写:

  d3.selectAll("circle")
      .attr('cx', 50)
      .attr('cy', 52)
      .attr('r', 24)
      .style("fill", "red");

selection.append用于创建一个新的元素, 选择其元素, 并在其元素后append数据.

  d3.select("body").append("h1")
      .text("Hello!");

或者创建多个小圆圈:

<svg height="960" width="960">
</svg>
<script src="d3.js"></script>
<script>
  var data = [5, 10, 15, 20, 25, 20, 15, 10, 5];
  var g = d3.select('svg').append('g'),
      circle = g.selectAll('circle').data(data);
  circle.enter()
      .append("circle")
      .attr('cx', function(d, i) { return (i + 1) * 32; })
      .attr('cy', function(d, i) { return d * 20; })
      .attr('r', function(d, i) { return 10; })
      .attr('fill', 'red');

</script>

 

Data

我们考虑下如下的图形如何绘制:

这里由五部分组成: 五个rect图形, x轴的索引值, y轴的索引值, x轴, y轴. 开些编写前, 先普及一些基本的svg函数:

1. rect: 用来绘制矩形, 基本属性如下:

x: 矩形左上角的x位置

y: 矩形左下角的y位置

width: 矩形的宽度

height: 矩形的高度

rx: 圆角的x方位的半径

ry: 圆角的y方位的半径

2. translate: 使元素进行移动

首先, 我们绘制五个rect:

  var rect_arr = [
    {"height": 40, "width": 100},
    {"height": 40, "width": 200},
    {"height": 40, "width": 300},
    {"height": 40, "width": 400},
    {"height": 40, "width": 500}
    ];

  var g = d3.select("svg").append("g").attr("transform", "translate(40,60)"),
      rect = g.selectAll("rect").data(rect_arr);
  rect.enter()
      .append("rect")
      .attr("class", "bar")
      .attr("height", function(d, i) { return d.height; })
      .attr("width", function(d, i) { return d.width; })
      .attr("y", function(d, i) { return 80 + i * 70; })
      .attr("fill", "steelblue");

效果图如下:

其次, 我们绘制x轴:

  var x_arr = [0, 100, 200, 300, 400, 500, 600],
      gx = d3.select("svg").append("g").attr("class", "x axis").attr("transform", "translate(40, 460)"),
      xaxis = gx.selectAll("g").data(x_arr);
  xaxis.enter()
      .append("g")
      .attr("transform", function(d, i) { return "translate(" + d + ",0)"})
      .attr("style", "opacity:1")
      .append("text")
      .attr("y", 14)
      .attr("x", 0)
      .attr("dy", ".71em")
      .attr("text-anchor", "middle")
      .text(function(d, i) { return i; });

和x轴的线:

  var xaxis_path = d3.select("svg g.x").append("path")
      .attr("stroke", "#000")
      .attr("d", "M0 6H600");

y轴:

  var y_arr = [
    {"y": 20, "text": "A"},
    {"y": 90, "text": "B"},
    {"y": 160, "text": "C"},
    {"y": 230, "text": "D"},
    {"y": 300, "text": "E"}
  ],
      gy = d3.select("svg").append("g").attr("class", "y axis").attr("transform", "translate(40, 100)"),
      yaxis = gy.selectAll("g").data(y_arr);
  yaxis.enter()
      .append("g")
      .attr("transform", function(d, i) { return "translate(0," + (40 + d.y) + ")"})
      .attr("style", "opacity:1")
      .append("text")
      .attr("x", "-8")
      .attr("y", 0)
      .attr("dy", ".32em")
      .attr("text-anchor", "end")
      .text(function(d, i) { return d.text; });

和y轴的线:

  var yaxis_path = d3.select("svg g.y").append("path")
      .attr("stroke", "#000")
      .attr("d", "M0 0V370");

 

我们来看以下作者给出的一个实例, 涉及到d3.js中的很多函数. 如果是初学d3.js, 需要查看文档才能看懂以下代码.

效果图:

具体代码:

<!DOCTYPE html>
<html>
<meta charset="utf-8">
<style>
  circle.dot {
    fill: steelblue;
  }
  .axis text {
    font: 10px sans-serif;
  }
  .axis path, .axis line {
    fill: none;
    stroke: #000;
    shape-rendering: crispEdges;
  }
</style>

<body>
<!--<script src="d3.js"></script>-->
<script src="http://d3js.org/d3.v2.min.js" charset="utf-8"></script>
<script>
  var data = [
    {x: 10.0, y: 9.14},
    {x: 8.0, y: 8.14},
    {x: 13.0, y: 8.74},
    {x: 9.0, y: 8.77},
    {x: 11.0, y: 9.26},
    {x: 14.0, y: 8.10},
    {x: 6.0, y: 6.13},
    {x: 4.0, y: 3.10},
    {x: 12.0, y: 9.13},
    {x: 7.0, y: 7.26},
    {x: 5.0, y: 4.74}
  ];
  var margin = {top: 40, right: 40, bottom: 40, left: 40},
      width = 960,
      height = 500;
  var x = pad(d3.scale.linear()
      .domain(d3.extent(data, function(d) { return d.x; }))
      .range([0, width - margin.left - margin.right]), 40);
  var y = pad(d3.scale.linear()
      .domain(d3.extent(data, function(d) { return d.y; }))
      .range([height - margin.top - margin.bottom, 0]), 40);
  var xAxis = d3.svg.axis()
      .scale(x)
      .orient("bottom")
      .tickPadding(8);
  var yAxis = d3.svg.axis()
      .scale(y)
      .orient("left")
      .tickPadding(8);
  var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height)
      .attr("class", "dot chart")
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  svg.selectAll(".dot")
      .data(data)
      .enter().append("circle")
      .attr("class", "dot")
      .attr("cx", function(d) { return x(d.x); })
      .attr("cy", function(d) { return y(d.y); })
      .attr("r", 12);

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + y.range()[0] + ")")
      .call(xAxis);

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis);

  function pad(scale, k) {
    var range = scale.range();
    if (range[0] > range[1]) k *= -1;
    return scale.domain([range[0] - k, range[1] + k].map(scale.invert)).nice();
  }
</script>
</body>
</html>

通过查询API文档, 解释一些基本的函数:

domain和range相伴相生, domain用来设置比例尺, 而range用来设置实际的长度. 例如在一个SVG内部(宽度为960), 我们绘制X轴, 则range的范围可以是[0,900], 而domain代表实际的数据(例如5个点, x轴分别为0, 1, 2, 3, 4), 则domain的范围就是[0,4]. 所以第一个点绘制在0处, 中间2绘制在450处, 最后一个点4绘制在900处.

d3.extent: 用来找出数组中的最大值和最小值.

axis().scale: 用来设置比例尺

备注: 此代码无法理解, 需要学习到后面, 多联系, 才可以自己完整写出来.

 

以上两个例子有难度. 现在来看看具体如何操作数据:

  svg.selectAll("circle")
      .data(data)
      .enter()
      .append("circle")
      .attr("cx", x)
      .attr("cy", y)
      .attr("r", 2.5);

这里我们可能有疑惑, 为什么circle还为存在情况下, 我们却selectAll("circle")呢?

这里是语法糖, 主要代码块应该这样看: selectAll("circle").data(data); 它的作用是将data和DOM元素关联起来, 使用enter()代表进入要递归的每个元素, 然后append()生成此元素. 所以代码实际上应该分开成:

  var circle = svg.selectAll("circle")
      .data(data);
  
  circle.enter()
      .append("circle")
      .attr("cx", x)
      .attr("cy", y)
      .attr("r", 2.5);

 

Scales&Axes

Data-->Attributes: 属性(attribute/style)是用来控制元素的位置和展现.

Domain-->Range: 用来控制data-space和visual-space.

  var x = d3.scale.linear()
      .domain([12, 24])
      .range([0, 720]);
  console.log(x(16)); // 240 = (16 - 12) * ((720 - 0) / (24 - 12)) + 0

我们可以使用d3.max/d3.min获取数组的最大最小值, 也可以使用d3.extent获取数组的最小最大值.

而extent甚至还可以接收一个对象和函数:

  var objects = [
    {"num": 1},
    {"num": 3},
    {"num": 2},
    {"num": 4}
  ];
  function value(d) { return d.num; }
  // [1,4]
  console.log(d3.extent(objects, value));

对于颜色, 我们也可以编写:

  var x = d3.scale.linear()
      .domain([12, 24])
      .range(["steelblue", "brown"]);
//  #ʚ586
  console.log(x(16));

甚至对像素, 我们也可以编写:

  var x = d3.scale.linear()
      .domain([12, 24])
      .range(["0px", "720px"]);
// 240px 
  console.log(x(16));

我们可以使用interpolate来更改所要显示的格式:

  var x = d3.scale.linear()
      .domain([12, 24])
      .range(["steelblue", "brown"])
      .interpolate(d3.interpolateHsl);
// #5f3cb0 
  console.log(x(16));

实际上, 对于domain/range, 不仅仅只支持两个参数, 允许传递多个参数:

  var x = d3.scale.linear()
      .domain([-10, 0, 100])
      .range(["red", "white", "green"]);
  console.log(x(-5)); // #ff8080
  console.log(x(50)); // #80c080

对于ordinal来说, 其domain和range是显式一一对应的:

  var x = d3.scale.ordinal()
      .domain(["A", "B", "C", "D"])
      .range([0, 10, 20, 30, 40]);
  console.log(x("B"));  // 10
  console.log(x("f"));  // 40

而ordinal经常用于categorical colors:

  var x = d3.scale.category20()
      .domain(["A", "B", "C", "D"])
  console.log(x("B"));  // #aec7e8 

d3.js提供的颜色:

而对于ordinal来说, 其range会依据domain进行等间距划分:

  var x = d3.scale.ordinal()
      .domain(["A", "B", "C", "D"])
      .rangePoints([0, 720]);
  console.log(x("B"));  // 240

我们可以通过给定的scale, 创建一个axis:

  var yAxis = d3.svg.axis()
      .scale(y)
      .orient("left");

然后将y轴绑定到指定的绘图区间:

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis);

而通过以上代码生成的html, 实际上大概如下:

由line进行线条的绘制, 而text显示坐标值. 所以我们通常需要为line或path进行style的设置:

  .axis path, .axis line {
    fill: none;
    stroke: #000;
    shape-rendering: crispEdges;
  }

 

我们一般使用transforms进行元素的移动:

 

后记:

1. HTML学习资料:

https://www.w3.org/TR/html5/

https://developers.whatwg.org/

http://diveintohtml5.info/

https://developer.mozilla.org/zh-CN/

2. SVG学习资料

https://www.w3.org/TR/SVG/

https://developer.mozilla.org/en-US/docs/Web/SVG

https://github.com/d3/d3/wiki/SVG-Shapes

3. CSS学习资料

https://www.w3.org/TR/CSS2/

https://www.w3.org/TR/selectors/

4. JavaScript学习资料

https://developer.mozilla.org/en-US/docs/Web/JavaScript

http://javascript.crockford.com/

5. D3学习资料

https://github.com/d3/d3/wiki/API-Reference

https://github.com/d3/d3/wiki

https://groups.google.com/forum/#!forum/d3-js

http://stackoverflow.com/questions/tagged/d3.js

6. 本学习资料来自:

https://bost.ocks.org/mike/d3/workshop/#0

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!