D3.js学习笔记六:让折线图数据可变

2015年02月15日 javascript 暂无评论 阅读 758 views 次

要解决的问题

现在这个统计图还要解决几个问题:数据长度可变、支持多个系列、为多系列加入图例。

现在统计图的数据长度是固定的,如果数据个数变化,那么多余的点还会存在,我们需要根据数据顶点个数来控制点的数量。如果数据是多个系列还要解决各个系列数据长度不同的问题。

实现可变长度的动画

首先调整产生数据系列的函数,使它产生不定长度的随机数,并且返回新数据的长度。

//产生随机数据
function getData()
{
var dataNum=Math.round(Math.round(Math.random()*10))+5;
dataset=[];
xMarks=[];
for(i=1;i<dataNum;i++)
{
dataset.push(Math.round(Math.random()*h));
xMarks.push("标签"+i);
}
return dataset.length;
}

然后我们改写drawChart()函数,使它能够适应新的长度的数据。实现横轴数据的动画,需要改写两处,一处是在初始化图形部分(函数外面),将添加横轴和取得横轴标签改写为并列而不是链式写法。

//添加横坐标轴
var xBar=svg.append("g")
.attr("class","axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);//通过编号获取对应的横轴标签
xBar.selectAll("text")
.text(function(d){return xMarks[d];});

然后修改函数体内的代码,将横纵轴的数据变动部分重新调整并设定动画,这里为了方便以后的调整动画时间,我们将它单独定义。

var newLength=getData();
var _duration=1000;//横轴数据动画
xScale.domain([0,newLength-1]);
xAxis.scale(xScale).ticks(newLength);
xBar.transition().duration(_duration).call(xAxis);
xBar.selectAll("text").text(function(d){return xMarks[d];});
xInner.scale(xScale).ticks(newLength);
xInnerBar.transition().duration(_duration).call(xInner);

//纵轴数据动画
yScale.domain([0,d3.max(dataset)]);
yBar.transition().duration(_duration).call(yAxis);
yInnerBar.transition().duration(_duration).call(yInner);

剩下的工作就是重绘对象并保持在底部,然后为它们设定动画(从底部升起),

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>画一个折线图</title>
<script type="text/javascript" src="js/d3.js"></script>
</head>
<style type="text/css">
body{
height: 100%;
}
.title{font-family:Arial,微软雅黑;font-size:18px;text-anchor:middle;}
.subTitle{font-family:Arial,宋体;font-size:12px;text-anchor:middle;fill:#666}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
fill:#999;
}
.inner_line path,
.inner_line line {
fill: none;
stroke:#E7E7E7;
shape-rendering: crispEdges;
}
</style>
<body>
<script type="text/javascript">
var dataset=[];
var xMarks=[];
var dataLength=0;
var w=600;
var h=400;
var padding=40;
//用一个变量存储标题和副标题的高度,如果没有标题什么的,就为0
var head_height=padding;
var title="收支平衡统计图";
var subTitle="2013年1月 至 2013年6月";
//用一个变量计算底部的高度,如果不是多系列,就为0
var foot_height=padding;
//模拟数据
dataLength=getData();
//定义画布
var svg=d3.select("body")
.append("svg")
.attr("width",w)
.attr("height",h);
//添加背景
svg.append("g")
.append("rect")
.attr("x",0)
.attr("y",0)
.attr("width",w)
.attr("height",h)
.style("fill","#FFF")
.style("stroke-width",2)
.style("stroke","#E7E7E7");
//添加标题
if(title!="")
{
svg.append("g")
.append("text")
.text(title)
.attr("class","title")
.attr("x",w/2)
.attr("y",head_height);
head_height+=30;
}
//添加副标题
if(subTitle!="")
{
svg.append("g")
.append("text")
.text(subTitle)
.attr("class","subTitle")
.attr("x",w/2)
.attr("y",head_height);
head_height+=20;
}
//横坐标轴比例尺
var xScale = d3.scale.linear()
.domain([0,dataset.length-1])
.range([padding,w-padding]);
//纵坐标轴比例尺
var yScale = d3.scale.linear()
.domain([0,d3.max(dataset)])
.range([h-foot_height,head_height]);
//定义横轴网格线
var xInner = d3.svg.axis()
.scale(xScale)
.tickSize(-(h-head_height-foot_height),0,0)
.tickFormat("")
.orient("bottom")
.ticks(dataset.length);
//添加横轴网格线
var xInnerBar=svg.append("g")
.attr("class","inner_line")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xInner);
//定义纵轴网格线
var yInner = d3.svg.axis()
.scale(yScale)
.tickSize(-(w-padding*2),0,0)
.tickFormat("")
.orient("left")
.ticks(10);
//添加纵轴网格线
var yInnerBar=svg.append("g")
.attr("class", "inner_line")
.attr("transform", "translate("+padding+",0)")
.call(yInner);
//定义横轴
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(dataset.length);
//添加横坐标轴
var xBar=svg.append("g")
.attr("class","axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//通过编号获取对应的横轴标签
xBar.selectAll("text")
.text(function(d){return xMarks[d];});
//定义纵轴
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(10);
//添加纵轴
var yBar=svg.append("g")
.attr("class", "axis")
.attr("transform", "translate("+padding+",0)")
.call(yAxis);
//添加折线
var line = d3.svg.line()
.x(function(d,i){return xScale(i);})
.y(function(d){return yScale(d);});
var path=svg.append("path")
.attr("d", line(dataset))
.style("fill","#F00")
.style("fill","none")
.style("stroke-width",1)
.style("stroke","#09F")
.style("stroke-opacity",0.9);
//添加系列的小圆点
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d,i) {
return xScale(i);
})
.attr("cy", function(d) {
return yScale(d);
})
.attr("r",5)
.attr("fill","#09F");
//重新作图
function drawChart()
{
var newLength=getData();
var _duration=1000;
//横轴数据动画
xScale.domain([0,newLength-1]);
xAxis.scale(xScale).ticks(newLength);
xBar.transition().duration(_duration).call(xAxis);
xBar.selectAll("text").text(function(d){return xMarks[d];});
xInner.scale(xScale).ticks(newLength);
xInnerBar.transition().duration(_duration).call(xInner);
//纵轴数据动画
yScale.domain([0,d3.max(dataset)]);
yBar.transition().duration(_duration).call(yAxis);
yInnerBar.transition().duration(_duration).call(yInner);
//重绘路径在底部
lineZero = d3.svg.line()
.x(function(d,i){return xScale(i);})
.y(h-foot_height);
path.attr("d",lineZero(dataset));
//路径动画
path.transition().duration(_duration).attr("d", line(dataset));
//清除圆点
svg.selectAll("circle").remove();
//添加圆点到底部
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d,i)
{
return xScale(i);
})
.attr("cy",h-foot_height)
.attr("r",5)
.attr("fill","#09F");
//圆点动画
svg.selectAll("circle")
.transition()
.duration(_duration)
.attr("cx", function(d,i) {
return xScale(i);
})
.attr("cy", function(d) {
return yScale(d);
})
dataLength=newLength;
}
//产生随机数据
function getData()
{
var dataNum=Math.round(Math.round(Math.random()*10))+5;
dataset=[];
xMarks=[];
for(i=1;i<dataNum;i++)
{
dataset.push(Math.round(Math.random()*h));
xMarks.push("标签"+i);
}
return dataset.length;
}
</script>
<p align="left">
<button onClick="javascript:drawChart();">刷新数据</button>
</p>
</body>
</html>

。点击【刷新数据】按钮,可以看到数据坐标轴变化,并且折线和标记从底部升起。

完善动画效果,实现平滑的过渡

我们需要的动画效果应该是连续和平滑的,要求已有的数据变化到新的位置,添加的数据从右侧补充进来。思路很简单,如果新数据比旧数据多,则添加新数据标记到右侧底部,否则就删除多出来的元素,图表拉伸。接下来我们改写代码,实现这个效果。

我们定义一个数组oldData保存旧的数据,以此产生动画开始的时候各个折线元素的初始值,使用exit().remove()方法删除圆点集合中的多余元素,。修改后的函数代码为:

//重新作图
function drawChart()
{
var newLength=getData();
var _duration=1000;//补足/删除路径
var lineZero = d3.svg.line()
.x(function(d,i){if(i>=oldData.length) return w-padding; else return xScale(i);})
.y(function(d,i){if(i>=oldData.length) return h-foot_height; else return yScale(oldData[i]);});

//路径初始化
path.attr("d",lineZero(dataset));

//截断旧数据
oldData=oldData.slice(0,dataset.length);
var circle=svg.selectAll("circle").data(oldData);

//删除多余的圆点
circle.exit().remove();

//圆点初始化,添加圆点,多出来的到右侧底部
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d,i){
if(i>=oldData.length) return w-padding; else return xScale(i);
})
.attr("cy",function(d,i){
if(i>=oldData.length) return h-foot_height; else return yScale(d);
})
.attr("r",5)
.attr("fill","#09F");

//横轴数据动画
xScale.domain([0,newLength-1]);
xAxis.scale(xScale).ticks(newLength);
xBar.transition().duration(_duration).call(xAxis);
xBar.selectAll("text").text(function(d){return xMarks[d];});
xInner.scale(xScale).ticks(newLength);
xInnerBar.transition().duration(_duration).call(xInner);

//纵轴数据动画
yScale.domain([0,d3.max(dataset)]);
yBar.transition().duration(_duration).call(yAxis);
yInnerBar.transition().duration(_duration).call(yInner);

//路径动画
path.transition().duration(_duration).attr("d",line(dataset));

//圆点动画
svg.selectAll("circle")
.transition()
.duration(_duration)
.attr("cx", function(d,i) {
return xScale(i);
})
.attr("cy", function(d) {
return yScale(d);
})

dataLength=newLength;
}

就是在新数据没有进入图表之前,用旧数据产生一系列的基于旧坐标的元素,如果有新元素,就放置在坐标【w-padding,h-foot_height】处,初始化位置之后,我们建立新的坐标系,然后真正的将新数据填充进图表,添加动画过渡效果。

6_1

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>画一个折线图</title>
<script type="text/javascript" src="js/d3.js"></script>
</head>
<style type="text/css">
body{
height: 100%;
}
.title{font-family:Arial,微软雅黑;font-size:18px;text-anchor:middle;}
.subTitle{font-family:Arial,宋体;font-size:12px;text-anchor:middle;fill:#666}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
fill:#999;
}
.inner_line path,
.inner_line line {
fill: none;
stroke:#E7E7E7;
shape-rendering: crispEdges;
}
</style>
<body>
<script type="text/javascript">
var dataset=[];
var oldData=[];
var xMarks=[];
var dataLength=0;
var w=600;
var h=400;
var padding=40;
//用一个变量存储标题和副标题的高度,如果没有标题什么的,就为0
var head_height=padding;
var title="收支平衡统计图";
var subTitle="2013年1月 至 2013年6月";
//用一个变量计算底部的高度,如果不是多系列,就为0
var foot_height=padding;
//模拟数据
dataLength=getData();
//定义画布
var svg=d3.select("body")
.append("svg")
.attr("width",w)
.attr("height",h);
//添加背景
svg.append("g")
.append("rect")
.attr("x",0)
.attr("y",0)
.attr("width",w)
.attr("height",h)
.style("fill","#FFF")
.style("stroke-width",2)
.style("stroke","#E7E7E7");
//添加标题
if(title!="")
{
svg.append("g")
.append("text")
.text(title)
.attr("class","title")
.attr("x",w/2)
.attr("y",head_height);
head_height+=30;
}
//添加副标题
if(subTitle!="")
{
svg.append("g")
.append("text")
.text(subTitle)
.attr("class","subTitle")
.attr("x",w/2)
.attr("y",head_height);
head_height+=20;
}
//横坐标轴比例尺
var xScale = d3.scale.linear()
.domain([0,dataset.length-1])
.range([padding,w-padding]);
//纵坐标轴比例尺
var yScale = d3.scale.linear()
.domain([0,d3.max(dataset)])
.range([h-foot_height,head_height]);
//定义横轴网格线
var xInner = d3.svg.axis()
.scale(xScale)
.tickSize(-(h-head_height-foot_height),0,0)
.tickFormat("")
.orient("bottom")
.ticks(dataset.length);
//添加横轴网格线
var xInnerBar=svg.append("g")
.attr("class","inner_line")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xInner);
//定义纵轴网格线
var yInner = d3.svg.axis()
.scale(yScale)
.tickSize(-(w-padding*2),0,0)
.tickFormat("")
.orient("left")
.ticks(10);
//添加纵轴网格线
var yInnerBar=svg.append("g")
.attr("class", "inner_line")
.attr("transform", "translate("+padding+",0)")
.call(yInner);
//定义横轴
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(dataset.length);
//添加横坐标轴
var xBar=svg.append("g")
.attr("class","axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//通过编号获取对应的横轴标签
xBar.selectAll("text")
.text(function(d){return xMarks[d];});
//定义纵轴
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(10);
//添加纵轴
var yBar=svg.append("g")
.attr("class", "axis")
.attr("transform", "translate("+padding+",0)")
.call(yAxis);
//添加折线
var line = d3.svg.line()
.x(function(d,i){return xScale(i);})
.y(function(d){return yScale(d);});
var path=svg.append("path")
.attr("d", line(dataset))
.style("fill","#F00")
.style("fill","none")
.style("stroke-width",1)
.style("stroke","#09F")
.style("stroke-opacity",0.9);
//添加系列的小圆点
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d,i) {
return xScale(i);
})
.attr("cy", function(d) {
return yScale(d);
})
.attr("r",5)
.attr("fill","#09F");
//重新作图
function drawChart()
{
var newLength=getData();
var _duration=1000;
//补足/删除路径
var lineZero = d3.svg.line()
.x(function(d,i){if(i>=oldData.length) return w-padding; else return xScale(i);})
.y(function(d,i){if(i>=oldData.length) return h-foot_height; else return yScale(oldData[i]);});
//路径初始化
path.attr("d",lineZero(dataset));
//截断旧数据
oldData=oldData.slice(0,dataset.length);
var circle=svg.selectAll("circle").data(oldData);
//删除多余的圆点
circle.exit().remove();
//圆点初始化,添加圆点,多出来的到右侧底部
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d,i){
if(i>=oldData.length) return w-padding; else return xScale(i);
})
.attr("cy",function(d,i){
if(i>=oldData.length) return h-foot_height; else return yScale(d);
})
.attr("r",5)
.attr("fill","#09F");
//横轴数据动画
xScale.domain([0,newLength-1]);
xAxis.scale(xScale).ticks(newLength);
xBar.transition().duration(_duration).call(xAxis);
xBar.selectAll("text").text(function(d){return xMarks[d];});
xInner.scale(xScale).ticks(newLength);
xInnerBar.transition().duration(_duration).call(xInner);
//纵轴数据动画
yScale.domain([0,d3.max(dataset)]);
yBar.transition().duration(_duration).call(yAxis);
yInnerBar.transition().duration(_duration).call(yInner);
//路径动画
path.transition().duration(_duration).attr("d",line(dataset));
//圆点动画
svg.selectAll("circle")
.transition()
.duration(_duration)
.attr("cx", function(d,i) {
return xScale(i);
})
.attr("cy", function(d) {
return yScale(d);
})
dataLength=newLength;
}
//产生随机数据
function getData()
{
var dataNum=Math.round(Math.round(Math.random()*10))+5;
oldData=dataset;
dataset=[];
xMarks=[];
for(i=1;i<dataNum;i++)
{
dataset.push(Math.round(Math.random()*h));
xMarks.push("标签"+i);
}
return dataset.length;
}
</script>
<p align="left">
<button onClick="javascript:drawChart();">刷新数据</button>
</p>
</body>
</html>

 

参考网站与相关文档资料


D3.js网站:http://d3js.org/

官方API说明https://github.com/mbostock/d3/wiki/API-Reference

学习资料:http://www.dashingd3js.com/

http://bost.ocks.org/mike/

https://github.com/mbostock/d3/wiki

https://groups.google.com/forum/?fromgroups#!forum/d3-js

http://stackoverflow.com/questions/tagged/d3.js

https://github.com/mbostock/d3/wiki/Gallery

给我留言

您必须 登录 才能发表留言!

Copyright © 大一网 保留所有权利.  

用户登录

分享到: