d3入门与实战

本文代码均已上传至Github,文章首发于d3入门与实战,转载请注明出处

1. 什么是d3

d3是一种用于数据可视化的工具。

2. 使用d3绘制简单的图形

d3.js可以将html中特定类型的元素转为图形:

1
2
3
4
5
<svg height="300" width="300">
<rect width="10" height="23" x="0" y="0"></rect>
<rect width="20" height="23" x="0" y="25"></rect>
<rect width="30" height="23" x="0" y="50"></rect>
</svg>

引入d3.js,打开该html,得到图片:
简单的图形
d3.js可以直接操作DOM,以下代码在body中添加一个svg,得到效果与上图一致:
html:

1
2
<body>
</body>

js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var dataArr = [10, 20, 30];
var dataHeight = 25;

d3.select('body')
.append('svg')
.attr('height', 300)
.attr('width', 300)
.selectAll('rect')
.data(dataArr)
.enter()
.append('rect')
.attr('width', function(d, i){
return d
})
.attr('height', dataHeight - 2)
.attr('x', 0)
.attr('y', function(d, i){
return i * dataHeight
})

js执行完成后的html:

1
2
3
4
5
6
7
<body>
<svg height="300" width="300">
<rect width="10" height="23" x="0" y="0"></rect>
<rect width="20" height="23" x="0" y="25"></rect>
<rect width="30" height="23" x="0" y="50"></rect>
</svg>
</body>

需要掌握的函数:

  • select/selectAll(condition): 选择器,选择方法与jQuery一致, select选择满足选择条件 condition 的第一个元素,selectAll选择满足选择条件 condition 的所有元素。本例通过select选择html的body元素
  • append(xxx): 在选中元素内部的末尾处添加元素xxx。本例选中body后在其内部的末尾添加svg元素
  • attr: 设置样式
  • data: 为所选元素集添加数据集。本例为svg中的所有rect添加对应元素集dataArr
  • enter: 为所选元素添加数据集时,若元素集数量少于数据集,自动添加相应数量的元素。本例中selectAll(‘rect’)选中的rect数量实际为0,执行完enter后添加了3个rect。
  • attr中的回调函数function(d, i):d为数据集中的数据data,i为数据集中该数据的位置index

3. 比例尺(Scale)

之前我们直接使用了数组dataset中的值设置了柱状图的尺寸,但大多情况下不适合将其直作为图形的参数,需要通过比例尺进行转换。
比例尺对数据的值(domain)进行映射,把一组输入的值映射至一定的输出范围(range)。
常用的比例尺有线性比例尺和序数比例尺两种。

3.1. 线性比例尺

1
2
3
4
5
6
7
8
9
10
11
var dataSet = [1, 2, 100, 30, 6];
var min = d3.min(dataSet);
var max = d3.max(dataSet);

var linear = d3.scale.linear()
.domain([min, max])
.range([0, 300]);

console.log(linear(1)); //0
console.log(linear(2)); //3.03...
console.log(linear(100)); //300

需要掌握的函数:

  • d3.scale.linear():建立并返回一个线性比例尺
  • domain([x, y]):设置比例尺的输入范围
  • range([x, y]):设置比例尺的输出范围
    通过上面的代码可以看到,通过比例尺完成了映射:([1, 100]) -> ([0, 300])

3.2. 序数比例尺

用于离散的映射,通过d3.scale.ordinal()可以建立一个离散的比例尺:

1
2
3
4
5
6
7
8
9
var xArr = [0, 1, 2, 3];
var yArr = ['a', 'b', 'c', 'd'];

var ordinal = d3.scale.ordinal()
.domain(xArr)
.range(yArr);

console.log(ordinal(0)); //a
console.log(ordinal(1)); //b

4. 坐标轴

坐标轴与比例尺一同使用,通过调用d3.svg.axis()建立:

1
2
3
4
var axis = d3.svg.axis()
.scale(linear)
.orient("bottom")
.ticks(5);

  • scale(): 调用参数为比例尺
  • orient: 指定坐标轴方向
  • ticks: 指定坐标轴的刻度数量

定义好坐标轴后,通过如下方式将坐标轴axis引入画板(为svg添加一个分组元素g, 通过axis(svg)将坐标轴引入svg):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var dataset = [1, 2, 3];

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

var axis = d3.svg.axis()
.scale(linear)
.orient("bottom")
.ticks(5);

var svg = d3.select('body')
.append('svg')
.attr('height', 300)
.attr('width', 300)
.append("g");

axis(svg);

效果图:
坐标轴
通常采用d3中的call(function)将以上代码写为如下形式:

1
2
3
4
5
6
var svg = d3.select('body')
.append('svg')
.attr('height', 300)
.attr('width', 300)
.append("g")
.call(axis);

d3中call()的参数是一个函数。调用之后,将当前的选择集作为参数传递给该函数。
默认坐标轴的样式不美观,可通过attr(xx, xx)修改坐标轴的样式。

5. 实战

5.1. 条线图

根据给出的数据源绘制出条线图。
要求:

  • 根据数据绘制条线图
  • 当鼠标hover在表上时,显示其详细信息

数据源:数据源
源码:Github源码
预览:在线预览
效果图
思路:

  1. 建立html:

    1
    2
    3
    4
    5
    6
    7
    <body>
    <div class="card">
    <div class="title">Gross Domestic Product, USA</div>
    <svg class="chart"></svg>
    <p>Units: Billions of Dollars Seasonal Adjustment: Seasonally Adjusted Annual Rate Notes: A Guide to the National Income and Product Accounts of the United States (NIPA) - (http://www.bea.gov/national/pdf/nipaguid.pdf)</p>
    </div>
    </body>
  2. 设置基本参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let chartMargin = {
    top: 15,
    right: 10,
    bottom: 30,
    left: 75
    };
    let months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    let width = $(".chart").width() - chartMargin.left - chartMargin.right;
    let height = $(".chart").height() - chartMargin.bottom - chartMargin.top;
  3. 获取x、y轴数据数据

    1
    2
    3
    4
    5
    6
    7
    $.getJSON("https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json", function(result) {
    let dataGDP = [];
    let dataTime = [];
    $.each(result.data, function(i, content) {
    dataTime.push(content[0]);
    dataGDP.push(content[1]);
    })
  4. 设置x、y轴

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let xScale = d3.time.scale()	//d3.time.scale()是针对日期和时间值的一个比例尺方法,可以对日期刻度作特殊处理
    .domain([new Date(dataTime[0]), new Date(dataTime[dataTime.length - 1])])
    .range([0, width]);
    let xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom")
    .ticks(d3.time.years, 5); //以年为单位,每5年作为一个刻度

    let yScale = d3.scale.linear()
    .domain([0, d3.max(dataGDP)])
    .range([height, 0]);
    let yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left")
    .ticks(10);
  5. 将坐标轴添加至图中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    svg.append("g")
    .attr("class", "axis")
    .attr("transform","translate(" + chartMargin.left + "," + (chartMargin.top + height) + ")")
    .call(xAxis)

    svg.append("g")
    .attr("class", "axis")
    .attr("transform","translate(" + chartMargin.left + "," + chartMargin.top + ")")
    .call(yAxis)
  6. 绘制reminder card(用于显示详细信息)

    1
    2
    3
    4
    let reminderCard = d3.select(".card")
    .append("div")
    .attr("class", "tooltip")
    .style("opacity", 0);
  7. 绘制chart

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let rects = svg.selectAll(".MyRect")
    .data(result.data)
    .enter()
    .append("rect").attr("class","MyRect")
    .attr("transform","translate(" + chartMargin.left + "," + chartMargin.top + ")")
    .attr("x", function(d, i){
    return xScale(new Date(d[0]));
    })
    .attr("y",function(d){
    return yScale(d[1]);
    })
    .attr("width", Math.ceil(width / result.data.length))
    .attr("height", function(d){
    return height - yScale(d[1]);
    })
    .attr("fill","steelblue")
  8. 添加鼠标hover显示详细信息功能-动画效果与监听器
    通过transition()方式为图画增加动画效果
    通过.on(xx, function(){…})增加监听器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    rects.on("mouseover", function(d) {
    let rect = d3.select(this);
    rect.attr("fill", "lightsteelblue");
    let currentDateTime = new Date(d[0]);
    let year = currentDateTime.getFullYear();
    let month = months[currentDateTime.getMonth()];
    let dollars = d[1];
    reminderCard.transition()
    .duration(200)
    .style("opacity", 0.9);
    reminderCard.html("<span class='amount'>"
    + dollars + "&nbsp;Billion </span><br><span class='year'>"
    + year + ' - ' + month + "</span>")
    .style("left", (d3.event.pageX + 5) + "px")
    .style("top", (d3.event.pageY - 50) + "px");
    });
    rects.on("mouseout", function() {
    let rect = d3.select(this);
    rect.attr("fill", "steelblue");
    reminderCard.transition()
    .duration(500)
    .style("opacity", 0);
    });

增加css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
.card{
text-align: center;
}

.chart{
width: 80%;
height: 80%;
min-height: 500px;
}

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


.axis text {
font-family: sans-serif;
font-size: 11px;
}

.tooltip {
position: absolute;
text-align: left;
width: auto;
height: auto;
padding: 3px 5px 3px 5px;
font: 1em sans-serif;
border: 0px;
border-radius: 5px;
pointer-events: none;
box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.2);
background-color: lightsteelblue;
}

5.2. 散点图

根据给出的数据源绘制出散点图。
要求:

  • 根据数据绘制散点图
  • 当鼠标hover在散点上时,显示其详细信息

数据源:数据源
源码:Github源码
预览:在线预览
效果图

思路:
大致过程类似于上一题,区别在于将append(‘rect’)变成了append(‘circle’):

1
2
3
4
5
6
7
8
9
rects.append("circle")	//添加的元素标签为circle
.attr("cx", function(d, i){ //设置横坐标位置
...
})
.attr("cy",function(d, i){ //设置纵坐标位置
...
})
.attr("r", 5) //设置圆的半径
...

本文首发于http://www.miaoyunze.com/,转载请注明出处