很久很久之前就了解过D3,但是一直没有着手开始学习。最近感觉之前的学习遇到瓶颈,进步不大,就又把D3拿出来学习了。
首先D3的官网:http://d3js.org/
中文学习网站:http://www.ourd3js.com/wordpress/
d3(data-driven documents)其实就是一个js库,可以用来把数据搞成图,官方的话就是把数据绑定到DOM元素上。d3js中有很多超级炫酷的例子。
接下来就简单介绍一下最近学习的几个画图的方法。
一 曲线图
在这之前说一下d3的使用,就是要引入一个js。注意!!这个js一定要放在自己写的d3js之前,js的执行是顺序的!我就在这里被自己蠢到了。
先上代码。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="css/style.css" media="screen">
<title>D3</title>
</head>
<body>
<div id = "container"></div>
<script src="js/d3.v3.js"></script>
<script src="js/line.js"></script>
</body>
</html>
在html中给出一个容器,用来盛放d3的svg就好了。要引入d3js。
画图用d3的函数来完成。
var width = 500,
height = 250,
margin = {left:50,top:30,right:20,bottom:20},
g_width = width - margin.left - margin.right,
g_height = height - margin.top - margin.bottom;
//svg
var svg = d3.select("#container") //选中container
.append("svg:svg") //添加svg元素
//width,height
.attr("width",width)
.attr("height",height)
var g = d3.select("svg")
.append("g")
.attr("transform","translate(" + margin.left+"," + margin.top +")") //g元素的偏移
var data = [1,3,5,7,8,4,3,7]
var scale_x = d3.scale.linear() //横向伸缩
.domain([0,data.length-1]) //输入范围
.range([0,g_width]) //输出范围
var scale_y = d3.scale.linear() //纵向伸缩
.domain([0,d3.max(data)])
.range([g_height,0]) //倒着写是为了让纵坐标在显示的时候是从下往上增大的(因为浏览器的左边原点在左上而数学习惯是在左下)
var line_generator = d3.svg.line() //d3的svg下的line函数
.x(
function(d,i){
return scale_x(i);
}) //x坐标点可以用数组data的下标来表示
.y(
function(d){
return scale_y(d);
}) //用数组实际的值
.interpolate("cardinal") //指定拟合方式
d3.select("g")
.append("path")
.attr("d",line_generator(data))
//d="M1,0L20,40L40,50L100,100L0,200" d代表path data M代表起点,坐标(1,0),L代表直线,后面的数字是拐点
//line_generator是我们定义的一个函数
var x_axis = d3.svg.axis().scale(scale_x),
y_axis = d3.svg.axis().scale(scale_y).orient("left"); //确定方向是在左边
g.append("g")
.call(x_axis)
.attr("transform","translate(0,"+g_height+")") //偏移
g.append("g")
.call(y_axis)
.append("text")
.text("Price($)")
.attr("transform","rotate(-90)") //旋转-90°
.attr("text-anchor","end")
.attr("dy","1em")
代码都有注释就不详细解释了,看一下效果:
二 面积图
面积图就是在曲线图的基础上,用曲线和x轴围出一个闭合的图形,然后填色就对了。
var width = 500,
height = 250,
margin = {left:50,top:30,right:20,bottom:20},
g_width = width - margin.left - margin.right,
g_height = height - margin.top - margin.bottom;
//svg
var svg = d3.select("#container") //选中container
.append("svg:svg") //添加svg元素
//width,height
.attr("width",width)
.attr("height",height)
var g = d3.select("svg")
.append("g")
.attr("transform","translate(" + margin.left+"," + margin.top +")")
var data = [1,3,5,7,8,4,3,7]
var scale_x = d3.scale.linear() //横向伸缩
.domain([0,data.length-1]) //输入范围
.range([0,g_width]) //输出范围
var scale_y = d3.scale.linear() //纵向伸缩
.domain([0,d3.max(data)])
.range([g_height,0]) //倒着写是为了让纵坐标在显示的时候是从下往上增大的
var area_generator = d3.svg.area() //生成面积的函数
.x(
function(d,i){
return scale_x(i);
}) //x坐标点可以用数组data的下标来表示
.y0(
g_height
) //y0是x轴
.y1(
function(d){
return scale_y(d);
}) //y1是那条曲线
.interpolate("cardinal") //指定拟合方式
d3.select("g")
.append("path")
.attr("d",area_generator(data))
//d="M1,0L20,40L40,50L100,100L0,200" d代表path data M代表起点,坐标(1,0),L代表直线,后面的数字是拐点
//line_generator是我们定义的一个函数
.style("fill","steelblue")
var x_axis = d3.svg.axis().scale(scale_x),
y_axis = d3.svg.axis().scale(scale_y).orient("left"); //确定方向是在左边
g.append("g")
.call(x_axis)
.attr("transform","translate(0,"+g_height+")")
g.append("g")
.call(y_axis)
.append("text")
.text("Price($)")
.attr("transform","rotate(-90)")
.attr("text-anchor","end")
.attr("dy","1em")
三 柱状图
这里画柱状图用了两种方法,一种是设定柱状图的长宽和间隔,然后将数据绑定到元素上,生成柱状图。另一种是用ordinal()函数来实现柱状图在x轴的伸缩。(教程里还讲了水平柱状图和竖直柱状图,原理是差不多的,这里只讲竖直的柱状图。)
1.设定长宽间隔来生成柱状图
d3.csv("data.csv",type,function(data){
console.log(data); //读取csv文件作为画图的数据
var bar_width = 50,
bar_padding = 10,
svg_width = (bar_width + bar_padding)*data.length,
svg_height = 500;
var scale = d3.scale.linear()
.domain([0,d3.max(data,function(d){return d.population;})])
.range([svg_height,0]); //因为屏幕的坐标原点在左上,所以这里为了符合我们的习惯(坐标原点在左下),输出范围是从大到0
var svg = d3.select("#contain")
.append("svg")
.attr("width",svg_width)
.attr("height",svg_height)
var bar = svg.selectAll("g") //选择还不存在的g元素
.data(data) //绑定数据
.enter() //enter选中只有数据没有元素的selection
.append("g")
.attr("transform",function (d,i) {
return "translate("+i*(bar_width+bar_padding)+",20)"; //这里注意Y轴偏移20是为了把标注的数字可以放在画布里,不然是放不下数字的
})
bar.append("rect") //生成矩形
.attr({
"y":function(d) {return scale(d.population);},
"height":function(d) {return svg_height - scale(d.population);},
"width":bar_width
})
.style("fill","steelblue")
bar.append("text")
.text(function(d){return d.year;})
.attr({
"y":function(d){return scale(d.population);},
"x":bar_width/2,
"text-anchor":"middle"
})
})
//将字符串转换为数值的形式
function type(d){
d.population =+ d.population;
return d;
}
这里注意到一点,这次画图的数据是导入了一个csv文件,而不是我们指定的数据。
csv格式的数据其实是一种纯文本的数据。
这里注意,读出的csv文件是字符串形式的,需要我们用函数将其转换为数值的形式。
并且,csv是有作用范围的。要将d3画图的内容都包裹在csv的作用范围内。
2 用ordinal()函数来生成柱状图
d3.csv("data.csv",type,function(data){
console.log(data);
var width = 1000,
height = 500,
margin = {left:30,top:30,right:30,bottom:30},
svg_width = width + margin.left + margin.right,
svg_height = height + margin.top + margin.bottom;
var scale = d3.scale.linear() //延y轴的缩放
.domain([0,d3.max(data,function(d){return d.population;})])
.range([height,0]); //因为屏幕的坐标原点在左上,所以这里为了符合我们的习惯(坐标原点在左下),输出范围是从大到0
var scale_x = d3.scale.ordinal() //延x轴的延伸
.domain(data.map(function(d){return d.year;}))
.rangeBands([0,width],0.1);
var svg = d3.select("#contain")
.append("svg")
.attr("width",svg_width)
.attr("height",svg_height)
var chart = svg.append("g")
.attr("transform","translate("+margin.left+","+margin.top+")");
var x_axis = d3.svg.axis().scale(scale_x),
y_axis = d3.svg.axis().scale(scale).orient("left"); //定义X轴Y轴
chart.append("g")
.call(x_axis)
.attr("transform","translate(0,"+height+")");
chart.append("g")
.call(y_axis); //添加X轴Y轴
var bar = chart.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr("class","bar") //因为上面xy轴用到了g,而这里选择g就会造成混乱,所以要给g加一个class为"bar",以示区分
.attr("transform",function (d) {
return "translate("+scale_x(d.year)+",0)";
})
bar.append("rect")
.attr({
"y":function(d) {return scale(d.population);},
"height":function(d) {return height - scale(d.population);},
"width":scale_x.rangeBand()
})
.style("fill","steelblue")
bar.append("text")
.text(function(d){return d.population;})
.attr({
"y":function(d){return scale(d.population);},
"x":scale_x.rangeBand()/2,
"text-anchor":"middle"
})
chart.append("g")
.call(y_axis)
.append("text")
.text("人口(亿)")
.attr("transform","rotate(-90)")
.attr("text-anchor","end")
.attr("dy","1em")
})
//将字符串转换为数值的形式
function type(d){
d.population =+ d.population;
return d;
}
这里的重点是在ordinal函数https://github.com/mbostock/d3/wiki/Ordinal-Scales#ordinal
四 饼状图
画饼状图就是画扇形,函数是d3.svg.arc(),参数有内外半径和起止的角度。
d3.csv("data1.csv",type,function (data) {
var width = 400,
height = 400;
var svg = d3.select("#contain")
.append("svg")
.attr("width",width)
.attr("height",height);
var g = svg.append("g")
.attr("transform","translate(200,200)"); //设置饼状图的圆心
var arc_generator = d3.svg.arc() //生成饼状图(扇形)的函数
.innerRadius(80) //内部的半径
.outerRadius(200) //外部半径
// .startAngle(0) //起始角度,竖直上面为0
// .endAngle(120*Math.PI/180); //结束角度,要转换为极坐标的形式 //这里是为了说明函数才规定了起止角度,实际应用中,角度是根据数据来确定的。
var angle_data = d3.layout.pie()
.value(function(d) {return d.population;}) //数据和角度的联系
console.log(angle_data(data));
var color = d3.scale.category10(); //这里是为了把不同的块设置为不同的颜色
g.selectAll("path") //选择还不存在的path
.data(angle_data(data)) //把data的数据传进去
.enter() //通过enter选中只有数据没有元素的selection
.append("path")
.attr("d",arc_generator)
.style("fill",function(d,i){return color(i);})
g.selectAll("text")
.data(angle_data(data))
.enter()
.append("text")
.text(function(d) {return d.data.education;})
.attr("transform",function(d){return "translate("+arc_generator.centroid(d)+")";}) //扇形取中心
.attr("text-anchor","middle");
});
function type(d){
d.population =+ d.population;
return d;
}
中间遇到了一个问题,就是本次引入的csv文件中有中文,所以,会存在编码问题。
csv文件的默认编码格式是ANSI编码,而我们用的是utf-8编码,所以在浏览器中就会出问题。
解决办法是,用记事本打开csv文件,然后另存的时候,会有选择编码的选项,选择utf-8就可以在浏览器中正常显示中文。
或者用csv函数本身的编码方式:
var csv = d3.dsv(",", "text/csv;charset=gb2312");
var tsv = d3.dsv(" ", "text/tab-separated-values;charset=gb2312");
以上是D3画基本统计图的方法。
后来看了一个会让图动起来的函数.transition()
比如把他写在两个颜色中间,就可以实现渐变色的动态效果。
.attr("fill","red")
//下面的代码是实现动态变化
.transition() //动态
.duration(3000) //持续时间
.ease("bounce") //变换方式
.delay(function(d,i){ //延时
return 200*i;
})
.attr("fill","steelblue");
或者来实现鼠标交互,就是鼠标放上去会有特效
//下面的代码是实现鼠标交互
.on("click",function(d,i){
d3.select(this)
.attr("fill","green");
})
.on("mouseover",function(d,i){
d3.select(this)
.attr("fill","yellow");
})
.on("mouseout",function(d,i){
d3.select(this)
.transition()
.duration(500)
.attr("fill","red");
});