我的目标是,注释100个d3.js的例子。

可能是史上最详细的 。

Area Chart
Basic Charts
里的第一个例子。

解析


1

var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;
    

纯粹的JavaScript代码。
首先定义上下左右margin的值。然后以960和500作为整个图表(包括空白部分)的宽和高,除去空白部分,算出图表真实的大小。这也就是x轴(width)和y轴(height)的长度。


2

var parseDate = d3.time.format("%d-%b-%y").parse;

使用了d3.time的Time Formatting。它用来在时间对象和字符串之间相互转换。
%d-%b-%y叫做specifier,表示的是“24-Mar-08”这种时间样式。parse是一个函数,赋值给parseDate变量,方便以后的调用。
整行代码的意思就是,把的一个函数赋给了parseDate,这个函数可以把长得像“24-Mar-08”的字符串转换成时间对象。


3

var x = d3.time.scale()
    .range([0, width]);
    
var y = d3.scale.linear()
    .range([height, 0]);
    

这两句都是和d3.scale相关的,所以放在一起讲。先提出一个问题来解释一下scale是做什么的。

如何从 0-100 映射到 0-1 ? 答案自然就是

  • 0 -> 0

  • 1 -> 0.01

  • 2 -> 0.02
    ...

以此类推。那从 100-0 映射到 0-1 呢? 从 1-12 到 1月到12月 呢? 或者从 0 - 1 判断 男女呢? scale就是做这个用的,它主要有三类:

  • 数字 (0-1)

  • 文字 (男女)

  • 时间 (月份)

而刚刚的例子中,0-100 叫做domain,可以理解为输入的值域。0-1 叫做range,就是输出的值域。

x这个scale就是时间,它输出的值域会在0width之间。y这个scale就是数字,它输出的值域会在height0之间。注意,这里并没有设置domain,也就是输入的值域。因为,输入值域当然要等到读取数据之后才会明了。

所以,从这里看出,xy的输出值域与widthheight有密切的关系。理所当然的,它们之后一定会被用作构图。


4

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

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

这两句看字面意义就可以了。它们和d3.svg.axis相关,是在建立坐标轴。
xAxis使用了上一段创建的scale x,同时指定了它的朝向是向下。意思是,水平的横轴,文字在轴的下面。以此类推,yAxis使用了scale y,垂直的纵轴,文字在轴的左边。


5

var area = d3.svg.area()
    .x(function(d) { return x(d.date); })
    .y0(height)
    .y1(function(d) { return y(d.close); });

建立了一个svgarea。区域 (area) 的意思是,对于一个xy0y1之间的部分表示此x覆盖的区域。

xy1都接受了一个函数,函数的形参是d。当渲染的时候,d3.js会把每一条数据都一一传进来。

为什么y0height,而y1d.close?

对于某个的xy0表示覆盖区域垂直方向的起始点,y1表示终止点。从d3.js的角度来说,左上角为(0, 0),对这个点将会绘制(x, y0=height)到(x, y1=d.close),也就是从图表的最底部往上画,画到d.close


6

var svg = d3.select("body").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

这部分比较简单,找到html里的body,在其末尾加上一个svg,并且指定了svg的高度的宽度。最后在svg里加入一个g(group),并且留出margin的距离。


7

d3.tsv("data.tsv", function (error, data) {
    if (error) throw error;
    

读取数据的函数,数据源是tsv(tab separated values)。当d3.js读取并且处理完数据,会执行这个function(error, data)这个callback。而数据会以数组的形式存放在形参data里面.


8

data.forEach(function(d) {
    d.date = parseDate(d.date);
    d.close = +d.close;
  });

既然data是数组,那我们就对它的每一个元素进行处理。
date使用之前保存下来的parseDate函数,记不记得,它能够处理类似24-Mar-08的字符串。
close仅仅是从字符串换成数字。


9

x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);

回忆一下,刚刚的xy作为scale只设定了与画图面积相关的range(输出值域)。现在读完数据了,需要设置domain(输入的值域)。
d3.extent是一个数组的辅助函数,可以返回其最小值和最大值。

完成以后,x scale的映射是,[时间最小值,时间最大值] -> [0, width],也就是xAxis的最左和最右。
y scale的映射是,[0,最大值] -> [height, 0],也就是yAxis的最低和最高。


10

最后几行比较无趣,一起说吧。

svg.append("path")
      .datum(data)
      .attr("class", "area")
      .attr("d", area);

用处理好的数据建立area。唯一要说一说的是这个datum,它一般用于静态的数据可视化。也就是说,如果这个元素不需要随着数据的变化而变化,那么用datum是合适的。更加具体的解释,或者是动态更新数据可视化的方法,参见这里

svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

建立x轴的g ——— ggroup的意思。除了建立的时候用了之前的xAxis,其他几行无非是加一些css class,然后下移到(0, height)位置,也就是图表的下方。

svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Price ($)");

建立y轴的group,同时加一个文字,转90度,设定初始位置和偏移量,同时改变字体本身锚的位置(一般都以左上角为锚)。


参考:

1 https://github.com/mbostock/d3/wiki/Gallery#basic-charts
2 http://bl.ocks.org/mbostock/3883195
3 https://gist.github.com/hugolpz/824446bb2f9bc8cce607


Shhh
2 声望2 粉丝

Be quite