准备
有了上一节的基础,我们需要再了解几个进阶的概念。
Scales
刻度,也就是把数据集重新调整刻度,比如创建一个线性刻度:
var normalize = d3.scale.linear().domain([0,50]).range([0,1])
domain(..)
用来设置输入数据的最大值和最小值,range(..)
用来设置输出数据的最大和最小值。这里代表将创建了一个线性刻度,将[0,50] 映射到 [0,1]。
验证一下:
Axes
坐标轴,可以说是可视化中非常重要的部分了,直接给一个例子:
// 创建一个svg元素
var svgElement = d3.select("body")
.append("svg")
.attr({"width" : 500, "height" : 500});
//创建一个线性刻度, domain(显示的实际刻度值) 是 [0,50] range(真实的长度) 是 [10,400]
var xScale = d3.scale.linear().domain([0,50]).range([10,400]);
// 基于此刻度生成坐标轴
var xAxis = d3.svg.axis().scale(xScale)
.ticks(5) // 限定刻度个数
.orient("bottom"); // 坐标轴label位置
// 包坐标轴都包在一组里g
var x = svgElement.append("g")
.call(xAxis);
样式:
path {
stroke: steelblue;
fill: none;
stroke-width: 2;
}
展示效果:
用d3.svg.axis()
方法建立坐标轴,ticks()
设置坐标轴拥有的刻度数, 如果调用这个函数则默认值是10。
数据加载
d3支持 JSON, CSV, TSV, XML,HTML等各种格式化的数据格式,并提供相应的加载api,例如加载TSV数据:
d3.tsv("xxxx/data.tsv", function (error, data) {
if (error)
console.log(error);
else
console.table(data);
});
创建基本的图表与代码复用
之前的概念都是为了建立各种经典可视化视图,下面就用线形图和区域图做入门示例。
线形图
线形图是应用最广泛的图表类型,通常是用来显示基于时间序列的数据表明随时间变化的趋势。
- 数据集
这里示例数据集仍然采用tsv的格式,是一个股票在一个时间跨度内的收盘价数据集合。
-
数据处理和格式转换
由于数据中心date和close都是字符串格式,在绘制之前需要将他们转换为可用的格式,在加载数据之前先进行数据的处理:
//Create a date parser var ParseDate = d3.time.format("%d-%b-%y").parse; //Read TSV file d3.tsv("data.tsv", function(error, data){ //Parse Data into useable format data.forEach(function(d){ d.date = ParseDate(d.date); d.close = +d.close; //the + sign converts numeric string to number }); //Print the data as a table in the console console.table(data); });
处理完后数据转换为:
这里d3.time.format.parse
函数可以将指定格式的时间数据转换为标准local时间格式;'+'号则强制类型转换将字符串转换为数字格式。 -
画线
我们在第一篇教程中讲过在svg中画线,需要给出每个点的坐标,这么多点是一个非常冗长的过程。在d3中我们只需要提供数据值和刻度d3就会自动帮我们完成这个繁重的工作,下面的代码就是一个可复用的线条生成器:
//Create a line generator var valueline = d3.svg.line() .x(function(d){ return xScale(d.date); }) .y(function(d){ return yScale(d.close); });
-
组合代码
根据前面两部分的步骤分解,我们在补充一下刻度的创建就可以完成整个绘图工作了,完整代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>line chart</title> <script src="https://d3js.org/d3.v3.js"></script> <style> path{ stroke: steelblue; fill: none; stroke-width: 2; } .axis path, .axis line { fill: none; stroke: grey; stroke-width: 1; /* 指定SVG元素<path>的渲染模式 */ shape-rendering: crispEdges; } </style> </head> <body> <script> //Set margins and sizes var margin = { top: 20, bottom: 50, right: 30, left: 50 }; var width = 700 - margin.left - margin.right; var height = 500 - margin.top - margin.bottom; //Create date parser var ParseDate = d3.time.format("%d-%b-%y").parse; //Create x and y scale to scale inputs var xScale = d3.time.scale().range([0, width]); var yScale = d3.scale.linear().range([height, 0]); //Create x and y axes var xAxis = d3.svg.axis().scale(xScale) .orient("bottom") .ticks(5); var yAxis = d3.svg.axis().scale(yScale) .orient("left") .ticks(5); //Create a line generator var valueline = d3.svg.line() .x(function (d) { return xScale(d.date); }) .y(function (d) { return yScale(d.close); }); //Create an SVG element and append it to the DOM var svgElement = d3.select("body").append("svg") .attr({ "width": width + margin.left + margin.right, "height": height + margin.top + margin.bottom }) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); //Read TSV file d3.tsv("./data.tsv", function (data) { //Parse Data into useable format data.forEach(function (d) { d.date = ParseDate(d.date); d.close = +d.close; //the + sign converts string automagically to number }); //Set the domains of our scales xScale.domain(d3.extent(data, function (d) { return d.date; })); // d3.extent - find the minimum and maximum value in an array. yScale.domain([0, d3.max(data, function (d) { return d.close; })]); //append the svg path var path = svgElement.append("path") .attr("d", valueline(data)); //Add X Axis var x = svgElement.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); //Add Y Axis var y = svgElement.append("g") .attr("class", "y axis") .call(yAxis); //Add label to y axis y.append("text") .attr("fill", "#000") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", "0.71em") .attr("text-anchor", "end") .text("Price ($)"); }); </script> </body> </html>
区域图
区域图和线形图唯一的区别就是在线下面标识出一片区域,因此只需要修改一点代码就可以变成区域图了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>area chart</title>
<script src="https://d3js.org/d3.v3.js"></script>
<style>
.axis path,
.axis line {
fill: none;
stroke: black;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script>
//Set margins and sizes
var margin = {
top: 20,
bottom: 50,
right: 30,
left: 50
};
var width = 960 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
//Create date parser
var ParseDate = d3.time.format("%d-%b-%y").parse;
//Create x and y scale to scale inputs
var xScale = d3.time.scale().range([0, width]);
var yScale = d3.scale.linear().range([height, 0]);
//Create x and y axes
var xAxis = d3.svg.axis().scale(xScale)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis().scale(yScale)
.orient("left");
//Create a area generator
var area = d3.svg.area()
.x(function (d) {
return xScale(d.date);
})
.y1(function (d) {
return yScale(d.close);
}); // area.y1 - get or set the y1-coordinate (topline) accessor.
//Create an SVG element and append it to the DOM
var svgElement = d3.select("body")
.append("svg").attr({ "width": width + margin.left + margin.right, "height": height + margin.top + margin.bottom })
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Read TSV file
d3.tsv("./data.tsv", function (data) {
//Parse Data into useable format
data.forEach(function (d) {
d.date = ParseDate(d.date);
d.close = +d.close;
//the + sign converts string automagically to number
});
//Set the domains of our scales
xScale.domain(d3.extent(data, function (d) { return d.date; }));
yScale.domain([0, d3.max(data, function (d) { return d.close; })]);
area.y0(yScale(0));
//append the svg path
var path = svgElement.append("path")
.attr("d", area(data))
.attr("fill", "steelblue");
//Add X Axis
var x = svgElement.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.call(xAxis);
//Add Y Axis
var y = svgElement.append("g")
.call(yAxis)
.attr("class", "y axis");
//Add label to Y axis
y.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Price ($)");
});
</script>
</body>
</html>
主要变化就是将线条生成器替换为区域生成器即可。
d3绘图基本步骤
根据上面两种图表的创建,我们可以提炼出一个比较标准的创建模块,按照如下步骤:
- 基本的html和css
- 预处理
a. 提前设定好图表的偏移、尺寸和变量等。
b. 创建刻度、坐标、色彩刻度等。
c. 创建数据处理函数例如时间解析器、百分比格式处理等。
- 可视化具体设计
这里根据不同的图表类型进行具体的代码,比如线条生成器和区域生成器。
- 创建svg
- 加载外部数据
- 组合代码
- 额外处理
包括标签、特效等。
这是一个推荐的比较标准的绘制步骤,并不是严格要求的,只是参考形成一个固定的风格,以便于理解和后期维护。下一节我们利用这些经验来绘制一个新的图表类型。
尝试一下
这一节按照上面的步骤来做一个柱状图,数据集采用csv格式:
- 基本html和css
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>bar chart</title>
<script src="https://d3js.org/d3.v3.js"></script>
<style>
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
/* 指定SVG元素<path>的渲染模式 */
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script>
- 预处理
//Set margins and sizes
var margin = {
top: 20,
bottom: 70,
right: 30,
left: 50
};
var width = 700 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
//Create date parser
var ParseDate = d3.time.format("%Y-%m").parse;
//Create x and y scale to scale inputs
var xScale = d3.scale.ordinal().rangeRoundBands([0, width], .05);
var yScale = d3.scale.linear().range([height, 0]);
//Create x and y axes
var xAxis = d3.svg.axis().scale(xScale)
.orient("bottom")
.tickFormat(d3.time.format("%Y-%m"));
var yAxis = d3.svg.axis().scale(yScale)
.orient("left")
.ticks(10);
- 可视化具体设计
柱状图不需要特别的设计。
- 创建svg
//Create an SVG element and append it to the DOM
var svgElement = d3.select("body").append("svg")
.attr({ "width": width + margin.left + margin.right, "height": height + margin.top + margin.bottom })
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
- 加载外部数据
//Read CSV file
d3.csv("./bar-data.csv", function (data) {
//Parse Data into useable format
data.forEach(function (d) {
d.date = ParseDate(d.date);
d.value = +d.value;
//the + sign converts string automagically to number
});
//Set the domains of our scales
xScale.domain(data.map(function(d) { return d.date; }));
yScale.domain([0, d3.max(data, function (d) { return d.value; })]);
- 组合代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>bar chart</title>
<script src="https://d3js.org/d3.v3.js"></script>
<style>
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
/* 指定SVG元素<path>的渲染模式 */
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script>
//Set margins and sizes
var margin = {
top: 20,
bottom: 70,
right: 30,
left: 50
};
var width = 700 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
//Create date parser
var ParseDate = d3.time.format("%Y-%m").parse;
//Create x and y scale to scale inputs
var xScale = d3.scale.ordinal().rangeRoundBands([0, width], .05);
var yScale = d3.scale.linear().range([height, 0]);
//Create x and y axes
var xAxis = d3.svg.axis().scale(xScale)
.orient("bottom")
.tickFormat(d3.time.format("%Y-%m"));
var yAxis = d3.svg.axis().scale(yScale)
.orient("left")
.ticks(10);
//Create an SVG element and append it to the DOM
var svgElement = d3.select("body").append("svg")
.attr({ "width": width + margin.left + margin.right, "height": height + margin.top + margin.bottom })
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Read CSV file
d3.csv("./bar-data.csv", function (data) {
//Parse Data into useable format
data.forEach(function (d) {
d.date = ParseDate(d.date);
d.value = +d.value;
//the + sign converts string automagically to number
});
//Set the domains of our scales
xScale.domain(data.map(function(d) { return d.date; }));
yScale.domain([0, d3.max(data, function (d) { return d.value; })]);
//Add X Axis
var x = svgElement.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
//Add Y Axis
var y = svgElement.append("g")
.attr("class", "y axis")
.call(yAxis);
//Add label to y axis
y.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Price ($)");
svgElement.selectAll("rect")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return xScale(d.date); })
.attr("width", xScale.rangeBand())
.attr("y", function(d) { return yScale(d.value); })
.attr("height", function(d) { return height - yScale(d.value); });
});
</script>
</body>
</html>
一个简单的柱状图就完成了:
其他常见图形
树状图、力导向图等常见的图形与新版本的d3写法也有不少变化,下面的教程再总结了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。