完整範例:http://wcc723.github.io/d3js/2014/10/17/Ironman-30-days-18/
有了資料跟一些D3.js基礎後,就可以嘗試把資料轉換成圖,雖然萬事已經具備,但要繪製成圖,還是需要費些心力。
在資料上,先前有提到高雄縣市合併有影響死亡率,所以這次還有再補上高雄縣市合併的資料。
這次還之前的不同,還有加上簡單的互動,而D3js的互動和jQuery寫法觀念相當接近,只是操作DOM時把$換成了d3,如果熟悉jquery的開發者很快就能上手。
範例大概如下:
svg.selectAll('.class').on('mouseover', function(d){
//do something
}).on('mouseout', function(d){
//do something
});
剩下的部分,就直接看code應該會比較理解~
$(function() {
var shPath = 'https://spreadsheets.google.com/feeds/list/',
shKey = '1hX3lqWLHFuwYiQeaBL0WevleUEOBAPKzshj2fJHogsM',
shCallback = '/public/values?alt=json-in-script&callback=?',
shList = [
{
'listKey': 'od6',
},{
'listKey': 'ol1cvs7',
}
,{
'listKey': 'objevh6',
} //這部分是高雄縣市的合併資料
]
var url = shPath + shKey + '/' + shList + shCallback; //合併路徑
var dataRemote = []; //建立空的陣列
//讀入每一個資料表
$.each(shList, function(i, list){
$.getJSON( shPath + shKey + '/' + list.listKey + shCallback)
.done(function (data) { //如果成功
list.dataName = []
var entry = data.feed.entry //只取feed entry的部分
var title = data.feed.title.$t //資料頁的標題
dataRemote.push({ //存回陣列
'title': title,
'data': entry
}); //送回dataset
jsonDone(); //執行驗證
})
.fail(function(jqxhr, textStatus, error){
console.log('GG,沒戲唱了'); //失敗
});
})
//驗證資料跑完沒
jsonDone = function(){
if (shList.length != dataRemote.length){
console.log('快好了'); //驗證未完成
} else if (shList.length == dataRemote.length){
console.log('好了', dataRemote); //驗證成功
dataset = dataRemote
runChart(); //繪製圖表
}
};
//真的開始畫圖了
runChart = function(){
var margin = {top: 60, right: 40, bottom: 50, left: 60};
var w = 560 ; // 寬
var h = 300 ; // 高
console.log(dataset[0].data.length)
datatime = []; //將時間存成陣列,作為Xscale用處
$.each(dataset[0].data,function(i,d){
datatime.push(d.gsx$time.$t)
});
var xScale = d3.scale.linear().domain([0,dataset[0].data.length]).range([0,w])
var yScale = d3.scale.linear().domain([.2, .85]).range([h, 0]);
//Y的Scale就不另外寫,先直接設定值
// 增加一個line function,用來把資料轉為x, y
var line = d3.svg.line()
.x(function(d,i) {
return xScale(i + 1); //利用尺度運算資料索引,傳回x的位置
})
.y(function(d) {
return yScale(d.gsx$percent.$t); //利用尺度運算資料的值,傳回y的位置
});
//增加一個SVG元素
var svg = d3.select('.demo').append('svg')
.attr('width', w + margin.left + margin.right) //將左右補滿
.attr('height', h + margin.top + margin.bottom) //上下補滿
.append('g') //增加一個群組g
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// 增加x軸線,tickSize是軸線的垂直高度,-h會往上拉高
var xAxis = d3.svg.axis().scale(xScale).ticks(5).orient('bottom').tickSize(-h).tickSubdivide(true);
// SVG加入x軸線
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + h + ')')
.call(xAxis);
// 建立y軸線,4個刻度,數字在左
var yAxisLeft = d3.svg.axis().scale(yScale).ticks(4).orient('left');
// SVG加入y軸線
svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(0,0)')
.call(yAxisLeft);
$.each(dataset,function(i,d){
svg.append('path').attr('d', line(d.data))
.style({
'stroke': d3.hsl((120 + 90*i), .6, .6),//d3.hsl是d3提供的顏色function
'stroke-width':1,
});//將每個資料繪製成折線,並且套用不同的色彩
svg.append('g').selectAll('circle').data(d.data).enter() //增加原點
.append('circle')
.attr({
'cx': function(d, i){return xScale(i + 1) },
'cy': function(d){return yScale(d.gsx$percent.$t)}, //x,y算法都類似line function
'r':'2px',
'class': 'dot' //這圓點主要是要給hover使用的
})
.style({
'fill': d3.hsl((120 + 90*i), .6, .6)//套用相同色彩
})
svg.append('g').append('text') //補上左上示意文字
.text(d.title)
.style({
'fill': d3.hsl((120 + 90*i), .6, .6),
'transform': 'translate(8px,'+ ((i * 15) + 12) //拉開間距
+'px)',
'font-size':'12px'
})
});
showTips = function(id){ //這部分是要作為Hover用的資料
date = dataset[0].data[id].gsx$time.$t //時間取其中一份的即可
var html = ''
$.each(dataset, function(i, dataset){ //將資料定義,並回傳
title = dataset.title;
percent = dataset.data[id].gsx$percent.$t;
html = html + '<div><span>'+title+'</span>/<span> '+percent+'%</span></div>'
});
html = '<div>'+date+'</div>' + html;
return html; //回傳整理好的html
};
svg.selectAll('.dot').on('mouseover', function(d){
//d3 function,類似jquery,直接可以控制class
var xPos = parseFloat(d3.select(this).attr('cx')) + margin.left //截取點的位置
var yPos = parseFloat(d3.select(this).attr('cy')) + margin.top
//這一個目標(d),裡面也包含了資料,所以d.gsx$id可以抓到這個點所包含的資料
var id = d.gsx$id.$t - 1 //抓取資料id
d3.select('#tooltip') //將div抓來用
.style({
'left': xPos + 'px',
'top': yPos + 'px'
})
.classed('hidden', false) //移除隱藏的class
.html(showTips(id)) //將剛剛的showTips function帶入
}).on('mouseout', function(d){ //如果移出的話
d3.select('#tooltip').classed('hidden', true); //補回剛剛的Class
});
}
});
有注意到嗎?在互動的事件上是使用一般的div,所以SVG與HTML是可以混合使用的,不過也記得要補上html部分的程式碼。
path {
stroke: DodgerBlue;
stroke-width: 1;
fill: none;
}
.axis {
font-size: 11px;
fill: gray;
}
.x.axis line {
stroke: lightgrey;
}
.x.axis .minor {
stroke-opacity: .5;
}
.x.axis path {
stroke: #fafafa;
}
.y.axis line, .y.axis path {
fill: none;
stroke: lightgrey;
}
.x.axis text{
display: none;
}
.demo{
position: relative;
}
#tooltip{
position: absolute;
max-width: 220px;
padding: 10px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 2px rgba(0,0,0, .16);
pointer-events: none;
transition: opacity .2s;
opacity: 1;
font-size: 11px;
}
#tooltip.hidden{
opacity: 0;
}