d3入门与实战

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

1. 什么是d3

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

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

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

<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:

<body>
</body>

js:

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:

<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. 线性比例尺

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()可以建立一个离散的比例尺:

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()建立:

var axis = d3.svg.axis()
    .scale(linear)
    .orient("bottom")
    .ticks(5);
  • scale(): 调用参数为比例尺
  • orient: 指定坐标轴方向
  • ticks: 指定坐标轴的刻度数量

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

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)将以上代码写为如下形式:

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:
    <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. 设置基本参数
    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轴数据数据
    $.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轴
    ```javascript
    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. 将坐标轴添加至图中
```javascript
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)
  1. 绘制reminder card(用于显示详细信息)
    let reminderCard = d3.select(".card")
    .append("div")
    .attr("class", "tooltip")
    .style("opacity", 0);
    
  2. 绘制chart
    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")
    
  3. 添加鼠标hover显示详细信息功能-动画效果与监听器
    通过transition()方式为图画增加动画效果
    通过.on(xx, function(){…})增加监听器
    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:
    ```css
    .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. 散点图
根据给出的[数据源](https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/cyclist-data.json)绘制出散点图。
要求:
- 根据数据绘制散点图
- 当鼠标hover在散点上时,显示其详细信息

数据源:[数据源](https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/cyclist-data.json)
源码:[Github源码](https://github.com/alivebao/My_FCC/tree/master/Data%20Viz/Scatterplot%20Graph)
预览:[在线预览](http://codepen.io/19920612/full/RKYBPX/)
![效果图](http://7xv88e.com1.z0.glb.clouddn.com/d3_exe_2_scatterplot_graph.PNG)

思路:
大致过程类似于上一题,区别在于将append('rect')变成了append('circle'):
```javascript
rects.append("circle")    //添加的元素标签为circle
  .attr("cx", function(d, i){    //设置横坐标位置
    ...
  })
  .attr("cy",function(d, i){    //设置纵坐标位置
    ...
  })
  .attr("r", 5)        //设置圆的半径
  ...