数据可视化:D3 基础入门学习笔记(二)

17 Apr 2018

Scale 比例尺

D3 的比例尺提供了一种计算关系, 将某一区域的值映射到另一区域,其大小关系不变。类似数学中一元二次方程的定义域和值域。

  • domain(): 定义域,指定原始数据范围;
  • range(): 值域,指定展示数据范围;

常用的比例尺:

  • d3.scaleLinear() 线性比例尺
    将一个连续的区间,映射到另一区间。
var data = [1, 5, 8, 10, 20];
var linear = d3.scaleLinear()
  .domain([d3.min(data), d3.max(data)])
  .range([0, 100]);

linear(1);// 0
linear(20); // 100
linear(5); // 21.052631578947366
  • d3.scaleOrdinal() 序数比例尺
    适用于定义域和值域是离散值的时候。
var data = [0, 1, 2, 3];
var color = ["red", "yellow", "blue", "green"];
var ordinal = d3.scaleOrdinal().domain(data).range(color);
ordinal(0); // red
ordinal(1); // yellow

Axis 坐标轴

SVG 中没有现成的坐标轴图形元素,需要用其他的元素组合构成。大致的结构如下:

<g>
    <!-- 坐标轴的轴线 -->
    <path></path>
    <!-- 第一个刻度 -->
    <g>
        <line></line> <!-- 第一个刻度的直线 -->
        <text></text> <!-- 第一个刻度的文字 -->
    </g>
    <!-- 第二个刻度 -->
    <g>
        <line></line> <!-- 第二个刻度的直线 -->
        <text></text> <!-- 第二个刻度的文字 -->
    </g>
...
</g>

如果手动绘制坐标轴的话会比较繁琐,D3 提供了坐标轴组件, 以下四个方法分别用于生成不同朝向的坐标轴:

  • d3.axisTop()
  • d3.axisBottom()
  • d3.axisLeft()
  • d3.axisRight()

生成坐标轴就需要用到比例尺。定义坐标轴,示例如下:

var dataset = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];

var linear = d3.scaleLinear()
  .domain([0, d3.max(dataset)])
  .range([0, 250]);

var axis = d3.axisBottom(linear) // 刻度方向朝下的坐标轴
  .ticks(5); //指定刻度的数量

在SVG中添加坐标轴:

svg.append("g")
  .attr("class","axis”) // 添加class, 方便修改样式
  .attr("transform","translate(20,130)”) // 修改坐标轴位置
  .call(axis);

👆不管坐标轴朝向如何,都是基于画布的原点(即左上角)绘制,要改变坐标轴位置的话需要用 translate 来移动位置。
在 D3 中,call() 的参数是一个函数。调用之后,将当前的选择集作为参数传递给里面的函数。上面示例中的 call() 的参数就是前面定义好的 axis 坐标轴函数。

Fetch Data 获取数据

D3 提供了不同格式文件的获取方式。简单列举其中的几种:

  • d3.csv
  • d3.tsv
  • d3.dsv
  • d3.json

。。。

示例

d3.dsv(",", "test.csv", function(d) {
    return {
        year: new Date(+d.Year, 0, 1), // convert "Year" column to Date
        make: d.Make,
        model: d.Model,
        length: +d.Length // convert "Length" column to number
    };
}).then(function(data) {
    console.log(data);
});

d3.csv 是针对以逗号分隔的 csv 文件(csv 文件的典型分隔符有逗号、分号等,Excel 表格可以另存为.csv格式),d3.dsv 和 d3.csv 的区别在于, d3.dsv 第一个参数可以指定分隔符。

👉 D3 5.0 开始使用 Promise 的写法替代之前的异步回调的写法,并且不向下兼容。

过渡 Transition

  • transition()
  • duration()
  • delay()
  • ease()

上面几个方法,顾名思义,主要是搭配着一起为图表添加各种过渡效果的。

综合示例 - 使用 D3 做一个简单的柱形图

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>bar chart</title>
  <style>
    .bar {
      fill: #129aee;
    }
    .bar:hover {
      fill: #0d74c5;
    }
    .axis--x path {
      display: none;
    }
  </style>
</head>
<body>
  <svg width="960" height="500"></svg>
  <script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
  <script type="text/javascript">
    var svg = d3.select("svg"),
      margin = {top: 20, right: 20, bottom: 30, left: 40},
      width = +svg.attr("width") - margin.left - margin.right,
      height = +svg.attr("height") - margin.top - margin.bottom;

    var g = svg.append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // scale
    // https://github.com/d3/d3-scale/blob/master/README.md#scaleBand
    var x = d3.scaleBand().range([0, width]).padding(0.2),
      y = d3.scaleLinear().range([height, 0]);

    d3.dsv(";", "data.csv", function(d) {
      d.frequency = + d.frequency;
      return d;
    }).then(function(data) {
      x.domain(data.map(function(d) { return d.letter; }));
      y.domain([0, d3.max(data, function(d) { return d.frequency; })]);

      // add axis-x
      g.append("g")
        .attr("class", "axis axis--x")
        .attr("transform", "translate(0," + height + ")")
        .call(d3.axisBottom(x));

      // add axis-y
      g.append("g")
        .attr("class", "axis axis--y")
        .call(d3.axisLeft(y).ticks(10, "%"))
        .append("text")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("dy", "0.71em")
        .attr("text-anchor", "end")
        .style("stroke", "black")
        .text("Frequency");

      // add bar
      g.selectAll(".bar")
        .data(data)
        .enter()
        .append("rect")
        .attr("class", "bar")
        .attr("width", x.bandwidth())
        .attr("height", 0)
        .attr("x", function(d) { return x(d.letter); })
        .attr("y", height)
        .transition() // 开启过渡
        .delay(function(d, i) { return i * 100 })
        .duration(1000)
        .ease(d3.easeBounce) // 弹跳效果
        .attr("height", function(d) { return height - y(d.frequency)})
        .attr("y",function(d){ return y(d.frequency) });
    });
  </script>
</body>
</html>

使用的数据:

letter;frequency
A;0.08167
B;0.01492
C;0.02782
D;0.04253
E;0.12702
F;0.02288
G;0.02015
H;0.06094
I;0.06966
J;0.00153
K;0.00772
L;0.04025
M;0.02406
N;0.06749
O;0.07507
P;0.01929
Q;0.00095
R;0.05987
S;0.06327
T;0.09056
U;0.02758
V;0.00978
W;0.0236
X;0.0015
Y;0.01974
Z;0.00074

👉 上面示例的效果是一个柱形图,用来展示英文中不同字母的使用频率。这个例子和使用的数据源于 D3 作者 Mike Bostock 的博文 Let’s Make a Bar Chart, III
基于作者写的例子简单做了一点修改,比如给每个柱形加上简单的过渡效果。

✨✨✨

推荐一下 D3 作者关于柱形图的这一系列文章,一共有三篇: