D3.js学习笔记七:多系列折线图与图例
http://www.daliane.com/d3_js_xue_xi_bi_ji_qi_duo_xi_lie_zhe_xian_tu_yu_tu_li/
要解決的問題
現(xiàn)在這個(gè)統(tǒng)計(jì)圖還要解決幾個(gè)問題:支持多個(gè)系列、為多系列加入圖例。
現(xiàn)在的數(shù)據(jù)是單條折線,如果有多條折線,那么需要為它指定不同的名稱和顏色,為它們指定圖例,指定圖例以后,我們通過圖例來控制折線的顯示和隱藏。
通過多維數(shù)組產(chǎn)生折線
首先調(diào)整產(chǎn)生數(shù)據(jù)系列的函數(shù),使它產(chǎn)生不定長度的隨機(jī)數(shù),每一個(gè)系列為一個(gè)數(shù)組,并指定折線名稱。
//產(chǎn)生隨機(jī)數(shù)據(jù)function getData()
{
var lineNum=Math.round(Math.random()*10)%3+1;
var dataNum=Math.round(Math.round(Math.random()*10))+5;
oldData=dataset;
dataset=[];
xMarks=[];
lineNames=[];for(i=0;i<dataNum;i++)
{
xMarks.push("標(biāo)簽"+i);
}
for(i=0;i<lineNum;i++)
{
var tempArr=[];
for(j=1;j<dataNum;j++)
{
tempArr.push(Math.round(Math.random()*h));
}
dataset.push(tempArr);
lineNames.push("系列"+i);
}
}
我們希望能夠自由的添加折線,最好的辦法就是將折線封裝起來,做成一個(gè)折線類,每次添加刪除就調(diào)用它的相關(guān)方法就行了,定義折線類,它有4個(gè)方法,init是第一次產(chǎn)生折線時(shí)候調(diào)用,它初始化內(nèi)部對(duì)象,movieBegin在數(shù)據(jù)更換之前調(diào)用,將圖表置于動(dòng)畫開始狀態(tài),reDraw開始數(shù)據(jù)動(dòng)畫,remove將折線從畫布清除。
//定義折線類function CrystalLineObject()
{
this.group=null;
this.path=null;
this.oldData=[];this.init=function(id)
{
var arr=dataset[id];
this.group=svg.append("g");var line = d3.svg.line()
.x(function(d,i){return xScale(i);})
.y(function(d){return yScale(d);});
//添加折線
this.path=this.group.append("path")
.attr("d",line(arr))
.style("fill","none")
.style("stroke-width",1)
.style("stroke",lineColor[id])
.style("stroke-opacity",0.9);
//添加系列的小圓點(diǎn)
this.group.selectAll("circle")
.data(arr)
.enter()
.append("circle")
.attr("cx", function(d,i) {
return xScale(i);
})
.attr("cy", function(d) {
return yScale(d);
})
.attr("r",5)
.attr("fill",lineColor[id]);
this.oldData=arr;
};
//動(dòng)畫初始化方法
this.movieBegin=function(id)
{
var arr=dataset[i];
//補(bǔ)足/刪除路徑
var olddata=this.oldData;
var line= 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]);});
//路徑初始化
this.path.attr("d",line(arr));
//截?cái)嗯f數(shù)據(jù)
var tempData=olddata.slice(0,arr.length);
var circle=this.group.selectAll("circle").data(tempData);
//刪除多余的圓點(diǎn)
circle.exit().remove();
//圓點(diǎn)初始化,添加圓點(diǎn),多出來的到右側(cè)底部
this.group.selectAll("circle")
.data(arr)
.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",lineColor[id]);
this.oldData=arr;
};
//重繪加動(dòng)畫效果
this.reDraw=function(id,_duration)
{
var arr=dataset[i];
var line = d3.svg.line()
.x(function(d,i){return xScale(i);})
.y(function(d){return yScale(d);});
//路徑動(dòng)畫
this.path.transition().duration(_duration).attr("d",line(arr));
//圓點(diǎn)動(dòng)畫
this.group.selectAll("circle")
.transition()
.duration(_duration)
.attr("cx", function(d,i) {
return xScale(i);
})
.attr("cy", function(d) {
return yScale(d);
})
};
//從畫布刪除折線
this.remove=function()
{
this.group.remove();
};
}
我們修改了drawChart()函數(shù),使得它針對(duì)不定數(shù)量的折線作出處理,如果少了,就加上,否則刪除多余的線條。
for(i=0;i<dataset.length;i++){
if(i<currentLineNum)
{
//對(duì)已有的線條做動(dòng)畫
lineObject=lines[i];
lineObject.movieBegin(i);
}
else
{
//如果現(xiàn)有線條不夠,就加上一些
var newLine=new CrystalLineObject();
newLine.init(i);
lines.push(newLine);
}
}//刪除多余的線條,如果有的話
if(dataset.length<currentLineNum)
{
for(i=dataset.length;i<currentLineNum;i++)
{
lineObject=lines[i];
lineObject.remove();
}
lines.splice(dataset.length,currentLineNum-dataset.length);
}
為系列添加圖例
我們添加一個(gè)圖例元素到畫布,并且將圖例的增刪改做成了一個(gè)函數(shù),代碼如下:
//添加圖例function addLegend()
{
var textGroup=legend.selectAll("text")
.data(lineNames);textGroup.exit().remove();legend.selectAll("text")
.data(lineNames)
.enter()
.append("text")
.text(function(d){return d;})
.attr("class","legend")
.attr("x", function(d,i) {return i*100;})
.attr("y",0)
.attr("fill",function(d,i){ return lineColor[i];});
var rectGroup=legend.selectAll("rect")
.data(lineNames);
rectGroup.exit().remove();
legend.selectAll("rect")
.data(lineNames)
.enter()
.append("rect")
.attr("x", function(d,i) {return i*100-20;})
.attr("y",-10)
.attr("width",12)
.attr("height",12)
.attr("fill",function(d,i){ return lineColor[i];});
legend.attr("transform","translate("+((w-lineNames.length*100)/2)+","+(h-10)+")");
}
這個(gè)是常規(guī)的功能,代碼雖然多,但是不難看懂,現(xiàn)在我們的折線圖如下圖所示。
察看新的動(dòng)畫演示效果:
| ? | <!DOCTYPE html> |
| ? | <html> |
| ? | <head> |
| ? | <meta?charset="utf-8"> |
| ? | <title>畫一個(gè)折線圖</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; |
| ? | } |
| ? | ? |
| ? | .legend{font-size: 12px; font-family:Arial, Helvetica, sans-serif} |
| ? | ? |
| ? | </style> |
| ? | <body> |
| ? | <script?type="text/javascript"> |
| ? | var dataset=[]; |
| ? | var lines=[]; //保存折線圖對(duì)象 |
| ? | var xMarks=[]; |
| ? | var lineNames=[]; //保存系列名稱 |
| ? | var lineColor=["#F00","#09F","#0F0"]; |
| ? | var w=600; |
| ? | var h=400; |
| ? | var padding=40; |
| ? | var currentLineNum=0; |
| ? | ? |
| ? | //用一個(gè)變量存儲(chǔ)標(biāo)題和副標(biāo)題的高度,如果沒有標(biāo)題什么的,就為0 |
| ? | var head_height=padding; |
| ? | var title="收支平衡統(tǒng)計(jì)圖"; |
| ? | var subTitle="2013年1月 至 2013年6月"; |
| ? | ? |
| ? | //用一個(gè)變量計(jì)算底部的高度,如果不是多系列,就為0 |
| ? | var foot_height=padding; |
| ? | ? |
| ? | //模擬數(shù)據(jù) |
| ? | getData(); |
| ? | ? |
| ? | //判斷是否多維數(shù)組,如果不是,則轉(zhuǎn)為多維數(shù)組,這些處理是為了處理外部傳遞的參數(shù)設(shè)置的,現(xiàn)在數(shù)據(jù)標(biāo)準(zhǔn),沒什么用 |
| ? | if(!(dataset[0] instanceof Array)) |
| ? | { |
| ? | var tempArr=[]; |
| ? | tempArr.push(dataset); |
| ? | dataset=tempArr; |
| ? | } |
| ? | ? |
| ? | //保存數(shù)組長度,也就是系列的個(gè)數(shù) |
| ? | currentLineNum=dataset.length; |
| ? | ? |
| ? | //圖例的預(yù)留位置 |
| ? | foot_height+=25; |
| ? | ? |
| ? | //定義畫布 |
| ? | 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"); |
| ? | ? |
| ? | //添加標(biāo)題 |
| ? | if(title!="") |
| ? | { |
| ? | svg.append("g") |
| ? | .append("text") |
| ? | .text(title) |
| ? | .attr("class","title") |
| ? | .attr("x",w/2) |
| ? | .attr("y",head_height); |
| ? | ? |
| ? | head_height+=30; |
| ? | } |
| ? | ? |
| ? | //添加副標(biāo)題 |
| ? | if(subTitle!="") |
| ? | { |
| ? | svg.append("g") |
| ? | .append("text") |
| ? | .text(subTitle) |
| ? | .attr("class","subTitle") |
| ? | .attr("x",w/2) |
| ? | .attr("y",head_height); |
| ? | ? |
| ? | head_height+=20; |
| ? | } |
| ? | ? |
| ? | maxdata=getMaxdata(dataset); |
| ? | ? |
| ? | //橫坐標(biāo)軸比例尺 |
| ? | var xScale = d3.scale.linear() |
| ? | .domain([0,dataset[0].length-1]) |
| ? | .range([padding,w-padding]); |
| ? | ? |
| ? | //縱坐標(biāo)軸比例尺 |
| ? | var yScale = d3.scale.linear() |
| ? | .domain([0,maxdata]) |
| ? | .range([h-foot_height,head_height]); |
| ? | ? |
| ? | //定義橫軸網(wǎng)格線 |
| ? | var xInner = d3.svg.axis() |
| ? | .scale(xScale) |
| ? | .tickSize(-(h-head_height-foot_height),0,0) |
| ? | .tickFormat("") |
| ? | .orient("bottom") |
| ? | .ticks(dataset[0].length); |
| ? | ? |
| ? | //添加橫軸網(wǎng)格線 |
| ? | var xInnerBar=svg.append("g") |
| ? | .attr("class","inner_line") |
| ? | .attr("transform", "translate(0," + (h - padding) + ")") |
| ? | .call(xInner); |
| ? | ? |
| ? | //定義縱軸網(wǎng)格線 |
| ? | var yInner = d3.svg.axis() |
| ? | .scale(yScale) |
| ? | .tickSize(-(w-padding*2),0,0) |
| ? | .tickFormat("") |
| ? | .orient("left") |
| ? | .ticks(10); |
| ? | ? |
| ? | //添加縱軸網(wǎng)格線 |
| ? | 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[0].length); |
| ? | ? |
| ? | //添加橫坐標(biāo)軸 |
| ? | var xBar=svg.append("g") |
| ? | .attr("class","axis") |
| ? | .attr("transform", "translate(0," + (h - foot_height) + ")") |
| ? | .call(xAxis); |
| ? | ? |
| ? | //通過編號(hào)獲取對(duì)應(yīng)的橫軸標(biāo)簽 |
| ? | 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 legend=svg.append("g"); |
| ? | ? |
| ? | addLegend(); |
| ? | ? |
| ? | //添加折線 |
| ? | lines=[]; |
| ? | for(i=0;i<currentLineNum;i++) |
| ? | { |
| ? | var newLine=new CrystalLineObject(); |
| ? | newLine.init(i); |
| ? | lines.push(newLine); |
| ? | } |
| ? | ? |
| ? | //重新作圖 |
| ? | function drawChart() |
| ? | { |
| ? | var _duration=1000; |
| ? | ? |
| ? | getData(); |
| ? | ? |
| ? | addLegend(); |
| ? | ? |
| ? | //設(shè)置線條動(dòng)畫起始位置 |
| ? | var lineObject=new CrystalLineObject(); |
| ? | ? |
| ? | for(i=0;i<dataset.length;i++) |
| ? | { |
| ? | if(i<currentLineNum) |
| ? | { |
| ? | //對(duì)已有的線條做動(dòng)畫 |
| ? | lineObject=lines[i]; |
| ? | lineObject.movieBegin(i); |
| ? | } |
| ? | else |
| ? | { |
| ? | //如果現(xiàn)有線條不夠,就加上一些 |
| ? | var newLine=new CrystalLineObject(); |
| ? | newLine.init(i); |
| ? | lines.push(newLine); |
| ? | } |
| ? | } |
| ? | ? |
| ? | //刪除多余的線條,如果有的話 |
| ? | if(dataset.length<currentLineNum) |
| ? | { |
| ? | for(i=dataset.length;i<currentLineNum;i++) |
| ? | { |
| ? | lineObject=lines[i]; |
| ? | lineObject.remove(); |
| ? | } |
| ? | lines.splice(dataset.length,currentLineNum-dataset.length); |
| ? | } |
| ? | ? |
| ? | maxdata=getMaxdata(dataset); |
| ? | newLength=dataset[0].length; |
| ? | ? |
| ? | //橫軸數(shù)據(jù)動(dòng)畫 |
| ? | 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); |
| ? | ? |
| ? | //縱軸數(shù)據(jù)動(dòng)畫 |
| ? | yScale.domain([0,maxdata]); |
| ? | yBar.transition().duration(_duration).call(yAxis); |
| ? | yInnerBar.transition().duration(_duration).call(yInner); |
| ? | ? |
| ? | //開始線條動(dòng)畫 |
| ? | for(i=0;i<lines.length;i++) |
| ? | { |
| ? | lineObject=lines[i]; |
| ? | lineObject.reDraw(i,_duration); |
| ? | } |
| ? | ? |
| ? | currentLineNum=dataset.length; |
| ? | dataLength=newLength; |
| ? | } |
| ? | ? |
| ? | //定義折線類 |
| ? | function CrystalLineObject() |
| ? | { |
| ? | this.group=null; |
| ? | this.path=null; |
| ? | this.oldData=[]; |
| ? | ? |
| ? | this.init=function(id) |
| ? | { |
| ? | var arr=dataset[id]; |
| ? | this.group=svg.append("g"); |
| ? | ? |
| ? | var line = d3.svg.line() |
| ? | .x(function(d,i){return xScale(i);}) |
| ? | .y(function(d){return yScale(d);}); |
| ? | ? |
| ? | //添加折線 |
| ? | this.path=this.group.append("path") |
| ? | .attr("d",line(arr)) |
| ? | .style("fill","none") |
| ? | .style("stroke-width",1) |
| ? | .style("stroke",lineColor[id]) |
| ? | .style("stroke-opacity",0.9); |
| ? | ? |
| ? | //添加系列的小圓點(diǎn) |
| ? | this.group.selectAll("circle") |
| ? | .data(arr) |
| ? | .enter() |
| ? | .append("circle") |
| ? | .attr("cx", function(d,i) { |
| ? | return xScale(i); |
| ? | }) |
| ? | .attr("cy", function(d) { |
| ? | return yScale(d); |
| ? | }) |
| ? | .attr("r",5) |
| ? | .attr("fill",lineColor[id]); |
| ? | this.oldData=arr; |
| ? | }; |
| ? | ? |
| ? | //動(dòng)畫初始化方法 |
| ? | this.movieBegin=function(id) |
| ? | { |
| ? | var arr=dataset[i]; |
| ? | //補(bǔ)足/刪除路徑 |
| ? | var olddata=this.oldData; |
| ? | var line= 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]);}); |
| ? | ? |
| ? | //路徑初始化 |
| ? | this.path.attr("d",line(arr)); |
| ? | ? |
| ? | //截?cái)嗯f數(shù)據(jù) |
| ? | var tempData=olddata.slice(0,arr.length); |
| ? | var circle=this.group.selectAll("circle").data(tempData); |
| ? | ? |
| ? | //刪除多余的圓點(diǎn) |
| ? | circle.exit().remove(); |
| ? | ? |
| ? | //圓點(diǎn)初始化,添加圓點(diǎn),多出來的到右側(cè)底部 |
| ? | this.group.selectAll("circle") |
| ? | .data(arr) |
| ? | .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",lineColor[id]); |
| ? | ? |
| ? | this.oldData=arr; |
| ? | }; |
| ? | ? |
| ? | //重繪加動(dòng)畫效果 |
| ? | this.reDraw=function(id,_duration) |
| ? | { |
| ? | var arr=dataset[i]; |
| ? | var line = d3.svg.line() |
| ? | .x(function(d,i){return xScale(i);}) |
| ? | .y(function(d){return yScale(d);}); |
| ? | ? |
| ? | //路徑動(dòng)畫 |
| ? | this.path.transition().duration(_duration).attr("d",line(arr)); |
| ? | ? |
| ? | //圓點(diǎn)動(dòng)畫 |
| ? | this.group.selectAll("circle") |
| ? | .transition() |
| ? | .duration(_duration) |
| ? | .attr("cx", function(d,i) { |
| ? | return xScale(i); |
| ? | }) |
| ? | .attr("cy", function(d) { |
| ? | return yScale(d); |
| ? | }) |
| ? | }; |
| ? | ? |
| ? | //從畫布刪除折線 |
| ? | this.remove=function() |
| ? | { |
| ? | this.group.remove(); |
| ? | }; |
| ? | } |
| ? | ? |
| ? | //添加圖例 |
| ? | function addLegend() |
| ? | { |
| ? | var textGroup=legend.selectAll("text") |
| ? | .data(lineNames); |
| ? | ? |
| ? | textGroup.exit().remove(); |
| ? | ? |
| ? | legend.selectAll("text") |
| ? | .data(lineNames) |
| ? | .enter() |
| ? | .append("text") |
| ? | .text(function(d){return d;}) |
| ? | .attr("class","legend") |
| ? | .attr("x", function(d,i) {return i*100;}) |
| ? | .attr("y",0) |
| ? | .attr("fill",function(d,i){ return lineColor[i];}); |
| ? | ? |
| ? | var rectGroup=legend.selectAll("rect") |
| ? | .data(lineNames); |
| ? | ? |
| ? | rectGroup.exit().remove(); |
| ? | ? |
| ? | legend.selectAll("rect") |
| ? | .data(lineNames) |
| ? | .enter() |
| ? | .append("rect") |
| ? | .attr("x", function(d,i) {return i*100-20;}) |
| ? | .attr("y",-10) |
| ? | .attr("width",12) |
| ? | .attr("height",12) |
| ? | .attr("fill",function(d,i){ return lineColor[i];}); |
| ? | ? |
| ? | legend.attr("transform","translate("+((w-lineNames.length*100)/2)+","+(h-10)+")"); |
| ? | } |
| ? | ? |
| ? | //產(chǎn)生隨機(jī)數(shù)據(jù) |
| ? | function getData() |
| ? | { |
| ? | var lineNum=Math.round(Math.random()*10)%3+1; |
| ? | var dataNum=Math.round(Math.round(Math.random()*10))+5; |
| ? | oldData=dataset; |
| ? | dataset=[]; |
| ? | xMarks=[]; |
| ? | lineNames=[]; |
| ? | ? |
| ? | for(i=0;i<dataNum;i++) |
| ? | { |
| ? | xMarks.push("標(biāo)簽"+i); |
| ? | } |
| ? | for(i=0;i<lineNum;i++) |
| ? | { |
| ? | var tempArr=[]; |
| ? | for(j=1;j<dataNum;j++) |
| ? | { |
| ? | tempArr.push(Math.round(Math.random()*h)); |
| ? | } |
| ? | dataset.push(tempArr); |
| ? | lineNames.push("系列"+i); |
| ? | } |
| ? | } |
| ? | ? |
| ? | //取得多維數(shù)組最大值 |
| ? | function getMaxdata(arr) |
| ? | { |
| ? | maxdata=0; |
| ? | for(i=0;i<arr.length;i++) |
| ? | { |
| ? | maxdata=d3.max([maxdata,d3.max(arr[i])]); |
| ? | } |
| ? | return maxdata; |
| ? | } |
| ? | </script> |
| ? | <p?align="left"> |
| ? | <button?onClick="javascript:drawChart();">刷新數(shù)據(jù)</button> |
| ? | </p> |
| ? | </body> |
| ? | </html> |
,打開后右鍵查看源碼,點(diǎn)擊【刷新數(shù)據(jù)】可以看到新的動(dòng)畫效果,加入了多系列支持和圖例,看起來比較接近水晶易表的折線圖了
總結(jié)
以上是生活随笔為你收集整理的D3.js学习笔记七:多系列折线图与图例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [附源码]SSM计算机毕业设计构建养猪场
- 下一篇: C++程序设计原理与实践(C++之父最作