1. 動畫與互動
在敘事結(jié)構(gòu)中全面應(yīng)用創(chuàng)意
D3如何幫你在可視化圖表中添加動畫與互動
用地理特征創(chuàng)建D3地圖
了解別人如何通過互動在可視化圖表中添加額外的效果,并完成引人入勝的故事
2. 案例研究:美國失業(yè)率
敘事精彩的交互式圖形范例:
人們的失業(yè)率
3. 交互性的好處
為什么交互性對數(shù)據(jù)可視化如此重要?
它可以幫助讀者了解比圖表中實(shí)際數(shù)據(jù)顯示的更多信息
如果有了懸浮文本、縮放、切換等功能,那么觀眾可以進(jìn)一步研究圖表和數(shù)據(jù)
plot.ly 上的 Gapminder
Gapminder 世界
4.迭代過程
之間學(xué)習(xí)了:
敘事結(jié)構(gòu)、給世界杯可視化圖添加背景
在世界杯可視化的例子中,我們了解到線型圖和散點(diǎn)圖不是傳遞數(shù)據(jù)信息的最佳方式,本課我們將進(jìn)一步迭代我們的圖形
在地圖上繪制數(shù)據(jù),給可視化圖表添加地理背景
通過D3動畫,利用時(shí)間效果,增強(qiáng)作者驅(qū)動敘事,最后通過讓讀者交互探索圖表,更精細(xì)地檢測數(shù)據(jù),創(chuàng)建雞尾酒杯的杯口結(jié)構(gòu)
5. 讓我們制作地圖
添加背景過程的第一步是創(chuàng)建地圖,而D3擁有很好的地理能力
創(chuàng)建交互地圖:
- 獲取數(shù)據(jù)
(使用JSON格式編碼你需要展示的坐標(biāo))
- shapefile
二進(jìn)制編碼信息,人類無法讀取,需要使用特殊的程序解讀
有存儲限制,比如你在指定的形狀里添加屬性(名字或者其他數(shù)據(jù))的數(shù)量
大小也有限制,從而限制了地圖中坐標(biāo)的真實(shí)精確度 - GeoJSON
本課程中使用,這在大多數(shù)情況下都夠用了
valid JSON/human readable/能方便地使用常見的開發(fā)工具(文本編輯器、瀏覽器控制臺)進(jìn)行探測和調(diào)試/數(shù)據(jù)文件更詳細(xì)更大,在網(wǎng)絡(luò)上創(chuàng)建地圖,我們經(jīng)常需要通過網(wǎng)絡(luò)請求發(fā)送此信息,我們的圖表會同時(shí)給瀏覽器和服務(wù)器施壓,并增加加載延遲, - TopoJSON
GeoJSON的擴(kuò)展,原始文件實(shí)際上比shapefile和GeoJSON要小
可以編碼拓?fù)浣Y(jié)構(gòu),但是GeoJSON不能
- 繪制數(shù)據(jù),即世界杯觀賽人數(shù)數(shù)據(jù)
6. GeoJSON 與 Shapefile
與Shapefile相比,GeoJSON 的優(yōu)勢在于:
- can be parsed by most programming languages
- human readable
地圖學(xué)校
請注意,地形(topography,地面的高度或形狀)與拓?fù)浣Y(jié)構(gòu)(topology,在這種情況下與地點(diǎn)之間的鄰接關(guān)系和連接有關(guān))這兩個(gè)詞拼法很像,但它們是不同的。TopoJSON 對拓?fù)浣Y(jié)構(gòu)編碼,不對地形進(jìn)行編碼。
7.什么是投影?
如果你想進(jìn)一步了解這些概念,mapschool.io 對地圖的諸多組成部分(拓?fù)浣Y(jié)構(gòu)、地理編碼、投影等)進(jìn)行了詳細(xì)的介紹。
用D3展示地圖和在散點(diǎn)圖中展示各點(diǎn)一樣
data domain → pixel range
data representation → screen representation 并在網(wǎng)頁上繪制
散點(diǎn)圖中:
日期表示年份
浮點(diǎn)數(shù)表示觀賽人數(shù)
這兩者都要轉(zhuǎn)換為像素值,即用圖表中x坐標(biāo)和y坐標(biāo)
scale()
D3中展示地圖:
地理坐標(biāo) → 像素值域
geographic → pixel range
經(jīng)度坐標(biāo)(x軸)/緯度坐標(biāo)(y軸) → 像素
latitude/longitude/ → pixels
mercator()
坐標(biāo)數(shù)據(jù)代表球形上的點(diǎn),實(shí)際上要在三維中編碼信息
由于地球是一個(gè)三維物體,我們沒辦法在二維平面展示,你能做的就是將三維物體用三維圖形表示出來,但這做起來通常比較復(fù)雜,你只能看到地球的一面
另一種方法是投影Projection
切割球體的表面,試著壓成平面
這是一種在更低維度上展示更高維度的東西而不丟失信息或失真的方法
mercator投影法 扭曲人口最少的地區(qū),即兩極附近的地區(qū)
Function mercator() use to convert latitude and longitude values to pixel values.
Longitude values are listed before latitude values in GeoJSON.
A map projection is the transformation of latitude and longitude values on a sphere to 2D coordinate points.
the mercator projection stretches areas near the poles .
8.地圖變形
Tthe mercator projection distorts area asymmetrically, and the distortion increases towards the ends of lines of longitude.
The mercator projection preserves area along lines of latitude.
The mercator projection is appropriate for our visualization because countries toward the middle of the map host and participate in the World Cup.
9.D3 中的地圖
world_countries.json 一個(gè)包含所有國家輪廓的GeoJSON文件
如果你想要可視化的地區(qū)沒有現(xiàn)成的GeoJSON文件,可以使用工具將shape files 轉(zhuǎn)換成GeoJSON
在你可以下載的代碼文件中,width 和 height 值略有不同。你可以在課程中調(diào)整這些值,看看可視化圖形將如何變化。
Ogre:將空間文件轉(zhuǎn)換為 GeoJSON
如何將形狀文件轉(zhuǎn)換為可在 Github 上使用的 GeoJSON(作者:Ben Balter)
如果你想了解 GeoJSON 值如何轉(zhuǎn)化為視覺表征,geojson.io 是一款交互式的 GeoJSON 編輯器。
10.檢查 GeoJSON
- 在var svg后加入debugger;
- 打開web服務(wù)器
- 讓控制臺捕捉debugger
- 控制臺輸入geo_data進(jìn)行查看
使用 GeoJSON 的另一個(gè)好處是它只是個(gè)JSON文件,我們可以像其他文件一樣傳送
我們甚至可以用D3標(biāo)準(zhǔn)的JSON數(shù)據(jù)加載函數(shù)來加載它
GeoJSON的特點(diǎn)在于其形狀擴(kuò)展,通過查看geo_data,我們發(fā)現(xiàn)每個(gè)國家都有一個(gè)geometry key ,對應(yīng)一個(gè)既有坐標(biāo)又有類型的對象
11.從 SVG 路徑繪制地圖
var projection = d3.geo.mercator(); #類似于我們?yōu)閳D表設(shè)置尺寸,我用scale將一個(gè)值/整數(shù)/浮點(diǎn)數(shù)轉(zhuǎn)換成像素點(diǎn)
var path = d3.geo.path().projection(projection) #構(gòu)建svg對象 繪制svg路徑來對地圖可視化
var map = svg.selectAll('path')
.data(geo_data.features) #features與國家坐標(biāo)的數(shù)組相對應(yīng)
.enter()
.append('path') #svg路徑元素非常靈活,能夠代表大多數(shù)形狀
.attr('d',path);
how does d3 know which country to draw?
the path variable is actually a function that gets passed the data is bound to each element in the selection.
12. 繪制并更改地圖
運(yùn)行上面的代碼,瀏覽器會出現(xiàn)一副地圖
但是有一些問題:北半球頂部不完整,南極洲又非常大
為了更好地確定地圖的位置,我們可以在投影中使用scale()和transform(),通過這兩個(gè)函數(shù),我們可以移動和操縱地圖的視覺化展示
scale():類似于谷歌地圖的放大和縮小功能
transform():類似將地圖的中心拖動至不同的位置
var projection = d3.geo.mercator()
.scale(220)
.translate([width/2,height/1.5]);
var path = d3.geo.path().projection(projection)
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d',path)
.style('fill','rgb(9,157,217)') #將地圖的填充色從黑色變?yōu)樗{(lán)色
.style('stroke','black') #將每個(gè)國家邊界的邊框線調(diào)整為深黑色線條,描邊的寬度稍細(xì)一些
.style('stroke-width',0.5)
在后面的代碼中,我們要下移地圖,將南極洲的那部分地圖切掉,因?yàn)槟蠘O洲沒有國家參與世界杯賽事
13.專題地圖
在地圖上加入背景信息,加入各年份世界杯的觀賽人數(shù)數(shù)據(jù)
用圓圈標(biāo)記世界杯的舉辦國,圓的半徑與該年的總觀賽人數(shù)成正比,這稱為主題地圖,指的是地圖中包含代表某個(gè)具體話題或者具體主題的數(shù)據(jù)
在我們的例子中,主題是世界杯,主題地圖的繪制,通常會通過在地圖上繪制一些數(shù)據(jù)添加一些額外的內(nèi)容
主題地圖的類型:
- dot maps
- Choropleth map 分級統(tǒng)計(jì)圖
根據(jù)區(qū)域?qū)Φ貓D標(biāo)色 - cartogram 變形地圖
根據(jù)數(shù)據(jù)值改變區(qū)域、形狀和尺寸 - 符號漸變地圖
是一個(gè)符號地圖 我們在地圖上繪制符號(圓圈,漸變),符號的區(qū)域和半徑會根據(jù)它們代表的數(shù)據(jù)而變化
14. 使用嵌套函數(shù)加載數(shù)據(jù)
在地圖上繪制代表觀賽人數(shù)的圓圈
- 載入觀賽人數(shù)數(shù)據(jù)
使用中間數(shù)據(jù)轉(zhuǎn)換函數(shù)把觀賽人數(shù)轉(zhuǎn)換成一個(gè)整數(shù),把日期轉(zhuǎn)換成JavaScript數(shù)據(jù)對象 - 傳遞至已定義好的plot_points函數(shù)
var projection = d3.geo.mercator()
.scale(220)
.translate([width/2,height/1.5]);
var path = d3.geo.path().projection(projection)
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d',path)
.style('fill','rgb(9,157,217)')
.style('stroke','black')
.style('stroke-width',0.5)
function plot_points(data) {
}
var format = d3.time.format("%d-%m-%Y(%H:%M h)");
d3.tsv("world_cup_geo,tsv",function(d) {
d['attendance'] = +d['attendance'];
d['date'] = format.parse(d['date']);
return d;
},plot_points);
總結(jié):
調(diào)用d3.json將world_countries.json載入到draw函數(shù)中,在draw函數(shù)里面調(diào)用d3.tsv,異步載入世界杯觀賽人數(shù)數(shù)據(jù),文件載入完畢后傳到plot_points函數(shù)。理論上,如果我們要載入更多數(shù)據(jù),我們可再次調(diào)用plot_points內(nèi)的d3.json,d3.tsv可無限嵌套,但使用太多的回調(diào)函數(shù)嵌套,不是一個(gè)好的做法
盡管我們在理論上可以無數(shù)次使用此方式來嵌套函數(shù),但最好還是要適度限制嵌套的次數(shù),以便簡化邏輯,使代碼更加易懂。
15. 嵌套函數(shù)
如需了解更多關(guān)于 D3 嵌套函數(shù)的信息,請查閱 D3 嵌套文檔和 D3 嵌套示例。
繪制地圖的第二步,我們需要通過自世界杯開賽以來舉辦的年份來給比賽分組,我們將比較世界杯前一年的觀賽人數(shù)和下一年的觀賽人數(shù)
d3在數(shù)據(jù)操作上的功能非常強(qiáng)大,為此我們可以使用d3的函數(shù)nest()來滿足我們的需求,而不是減少數(shù)據(jù)并在上面聚合
使用key()函數(shù),把它傳遞到訪問器回調(diào),無論這個(gè)回調(diào)返回什么都是嵌套分組的值
一旦你用某種方式給數(shù)據(jù)分組完畢,你需要以一些有意義的方式將其聚合
function plot_points(data) {
var nested = d3.nest()
.key(function(d) {
})
.rollup(function(leaves) {
})
.entries(data);
};
16.聚合數(shù)據(jù)
function plot_points(data) { #用console.table(data.slice(0,10))檢查數(shù)據(jù)
//draw circles logic
debugger;
var nested = d3.nest()
.key(function(d) { #審查d,發(fā)現(xiàn)它是第一場比賽
debugger;
return d['date'].getUTCFullYear(); #審查d['date'].getUTCFullYear();得到1934
})
.rollup(function(leaves) { #傳遞給rollup()函數(shù)的是在key()函數(shù)指定的一個(gè)分組,rollup()函數(shù)的任務(wù)是將17個(gè)對象/比賽提煉成一個(gè)值,或者是一個(gè)聚合
debugger;
return "";
})
.entries(data);
};
完成rollup()函數(shù),每一組需要三個(gè)東西:
- 每一年比賽的總觀賽人數(shù) 使用d3.sum
function plot_points(data) {
//draw circles logic
debugger;
var nested = d3.nest()
.key(function(d) {
return d['date'].getUTCFullYear();
})
.rollup(function(leaves) {
debugger;
d3.sum(leaves,function(d) {
return d['attendance'];
});
- 地圖上圓圈的經(jīng)度
- 地圖上圓圈的緯度
17. 采集體育館的地理位置
function plot_points(data) {
//draw circles logic
debugger;
var nested = d3.nest()
.key(function(d) {
return d['date'].getUTCFullYear();
})
.rollup(function(leaves) {
debugger;
var total = d3.sum(leaves,function(d) {
return d['attendance'];
});
var coords = leaves.map(function(d) {
return projection ([+d.long,+d.lat]);
})
.entries(data);
};
map()函數(shù)會轉(zhuǎn)換數(shù)組的每個(gè)元素,再返回一個(gè)數(shù)據(jù),回調(diào)函數(shù)中傳遞的d代表leaves的每個(gè)元素,不論返回何值,都會存儲到返回?cái)?shù)組coords中
本例中,我們想將數(shù)據(jù)點(diǎn)的經(jīng)緯度對應(yīng)至某些像素值,這可以通過projection()函數(shù)得到,輸入經(jīng)緯度,得到像素x和y
像素到底有什么用?
如果某年有四個(gè)場館舉辦比賽,那么我首先把每個(gè)場館的經(jīng)緯度轉(zhuǎn)化成x,y像素值,最后我們要做的是計(jì)算所有場館x和y的平均值,得到它們的中心定位
18.平均化位置
function plot_points(data) {
//draw circles logic
debugger;
var nested = d3.nest()
.key(function(d) {
return d['date'].getUTCFullYear();
})
.rollup(function(leaves) {
debugger;
var total = d3.sum(leaves,function(d) {
return d['attendance'];
});
var coords = leaves.map(function(d) {
return projection ([+d.long,+d.lat]);
});
var center_x= d3.mean(coord,function(d) {
return d[0];
});
var center_y= d3.mean(coord,function(d) {
return d[1];
});
})
.entries(data);
};
19.檢查嵌套返回
聚合要做的最后一步,是返回某些存儲在rollup()返回的最終結(jié)果中的對象
本例中,我們只返回'attendance'的值和center_x,center_y
function plot_points(data) {
var nested = d3.nest()
.key(function(d) {
return d['date'].getUTCFullYear();
})
.rollup(function(leaves) {
var total = d3.sum(leaves, function(d) {
return d['attendance'];
});
var coords = leaves.map(function(d) {
return projection([+d.long, +d.lat]);
});
var center_x = d3.mean(coords, function(d) {
return d[0];
});
var center_y = d3.mean(coords, function(d) {
return d[1];
});
return {
'attendance' : total,
'x' : center_x,
'y' : center_y
};
})
.entries(data);
debugger;
};
20.向地圖添加圓圈
圓圈半徑表示該年的觀賽人數(shù)
svg.append('g')
.attr("class", "bubble")
.selectAll("circle")
.data(nested)
.enter()
.append("circle")
.attr('cx',function(d) {return d.values['x'];})
.attr('cy',function(d) {return d.values['y'];})
.attr('r',5);
21. 確定觀賽人數(shù)圓圈的大小
設(shè)置合適的圓圈比例,讓其正確反映觀賽人數(shù)
22. 如何用圓圈撒謊
在使用圓圈或其他任意形狀進(jìn)行視覺編碼時(shí),你應(yīng)該特別注意面積或體積所展現(xiàn)的數(shù)據(jù)。如果你不仔細(xì)處理數(shù)據(jù)及其視覺編碼,你可能就會錯(cuò)誤地展現(xiàn)數(shù)據(jù),報(bào)告夸大的發(fā)現(xiàn)結(jié)果,更糟糕的也許是失去讀者的信任。
以這兩個(gè)圖形為例。原始設(shè)計(jì)刊登在 Vox Media 撰寫的文章《關(guān)于冰桶挑戰(zhàn)的真相》中,并且圖形的更正版本在之后也得到發(fā)布。這篇文章現(xiàn)在呈現(xiàn)的是更正版本,原始圖形是這個(gè)。
注意:以下為出現(xiàn)在文章底部的文字。
更正:在此文章的早期版本中,圖形圓圈的大小沒有準(zhǔn)確地反映數(shù)據(jù)。
如果你不熟悉冰桶挑戰(zhàn),請查閱文章。你可能還從參加捐獻(xiàn)活動的名人或你自己的家人那里看到過 YouTube 視頻。
通過并排比較,你應(yīng)該注意到原始圖形中的圓圈要比 Vox 網(wǎng)站上更正圖形內(nèi)的圓圈大得多。
讓我們借此學(xué)習(xí)機(jī)會,理解如何使用圓圈面積準(zhǔn)確展現(xiàn)數(shù)據(jù)值。
原始圖形中的問題是數(shù)據(jù)值被用于繪制圓圈的半徑。如果你將數(shù)據(jù)值用于圓圈半徑,那么圓圈面積將是半徑平方的三倍左右。
圓面積 = π*r2
或
圓面積 ≈ 3.14*r2
例如,數(shù)據(jù)值 4 將創(chuàng)建面積為 16π 的圓。數(shù)據(jù)值 5 將創(chuàng)建面積為 25π 的圓。
我們頗為高效地將數(shù)據(jù)值進(jìn)行平方,用來創(chuàng)建新的視覺表征。這造成圖形中圓圈的外觀要比其應(yīng)該展現(xiàn)的大得多。
要避免這個(gè)問題,數(shù)據(jù)值應(yīng)匹配圓圈的面積。你可以將數(shù)據(jù)值開平方,以確定每個(gè)圓圈的半徑。
Jonathan 將在后面的幾段視頻中解釋如何使用代碼來完成這一操作。他將利用匿名讀取函數(shù)和 d3.scale.sqrt()。
對于這道練習(xí)題,你應(yīng)該思考如何重新設(shè)計(jì)和改善圖形。請隨時(shí)在討論區(qū)中分享你的想法、示意圖或可視化圖形。
如需了解圖形改善和重新設(shè)計(jì)的額外信息,請?jiān)L問以下相關(guān)閱讀鏈接。
相關(guān)閱讀
虛假可視化:記者把可視化圖弄錯(cuò)了(作者:Randy Krum)
惱人的氣泡圖(作者:David Mendoza)
23. 半徑標(biāo)尺
var attendance_max = d3.max(nested, function(d) {
return d.values['attendance'];
});
var radius = d3.scale.sqrt()
.domain([0, attendance_max])
.range([0, 15]);
svg.append('g')
.attr("class", "bubble")
.selectAll("circle")
.data(nested)
.enter()
.append("circle")
.attr('cx', function(d) { return d.values['x']; })
.attr('cy', function(d) { return d.values['y']; })
.attr('r', function(d) {
return radius(d.values['attendance']);
});
24. 調(diào)整地圖設(shè)計(jì)
svg.append('g')
.attr("class", "bubble")
.selectAll("circle")
.data(nested)
.enter()
.append("circle")
.attr('cx', function(d) { return d.values['x']; })
.attr('cy', function(d) { return d.values['y']; })
.attr('r', function(d) {
return radius(d.values['attendance']);
})
.attr('fill', 'rgb(247, 148, 32)') #填充色為橙色
.attr('stroke', 'black') #對圓圈設(shè)置黑色描邊
.attr('stroke-width', 0.7) #描邊較細(xì)
.attr('opacity', 0.7); #增加透明度以便看清所有重疊的圓圈
對于多次舉辦世界杯的國家,圓圈可能出現(xiàn)重疊,但是小圓總是在上方,這是因?yàn)楹笃谑澜绫^賽人數(shù)增加
如果為了增強(qiáng)地圖效果而添加新數(shù)據(jù),要確保杜絕遮蔽現(xiàn)象
25.就繪圖順序?qū)?shù)據(jù)進(jìn)行排序
通過對數(shù)據(jù)分類,可以避免遮蔽現(xiàn)象
查看有關(guān) JavaScript 排序函數(shù)的文檔。
svg.append('g')
.attr("class", "bubble")
.selectAll("circle")
.data(nested.sort(function(a, b){
return b.values['attendance'] - a.values['attendance'];
}))
.enter()
.append("circle")
.attr('cx', function(d) { return d.values['x']; })
.attr('cy', function(d) { return d.values['y']; })
.attr('r', function(d) {
return radius(d.values['attendance']);
})
.attr('fill', 'rgb(247, 148, 32)')
.attr('stroke', 'black')
.attr('stroke-width', 0.7)
.attr('opacity', 0.7);
.data(nested.sort(function(a, b){
return b.values['attendance'] - a.values['attendance'];
返回值小于0,a位于首位,可以說a是本函數(shù)的第一個(gè)參數(shù)
返回值大于0,b位于首位,可以說b是本函數(shù)的第二個(gè)參數(shù)
返回值等于0,則沒有位置變化,此時(shí)只要a和b保持原來的順序即可
總的來說就是首先繪制人數(shù)多的
26. 我們在哪兒
我們?yōu)榱吮A艨臻g信息而犧牲了時(shí)間信息
我們知道,世界杯舉辦年份對觀賽人數(shù)的影響相當(dāng)大,因?yàn)殡S著時(shí)間推移,世界杯觀賽人數(shù)穩(wěn)步增加
地理和時(shí)間信息并得的方法是用動畫表現(xiàn)時(shí)間過程
可將動畫看作另一種視覺編碼,幫我們傳遞變化的時(shí)間數(shù)據(jù)

杯底:靜態(tài)圖,是起點(diǎn)
杯莖:通過動畫的作者驅(qū)動敘述
杯口:通過互動/交互的讀者驅(qū)動敘述
27. 更新函數(shù)
要用動畫呈現(xiàn)世界杯年份,需要做兩件事:
- 使用函數(shù)更新地圖
因?yàn)槲覀儠v年數(shù)據(jù)反復(fù)調(diào)用更新,我們要把數(shù)據(jù)封裝到一個(gè)能讓我們隨時(shí)調(diào)用的函數(shù)中 - 對世界杯年份進(jìn)行循環(huán),并將年份傳遞給更新函數(shù)
28. 概述更新函數(shù)
函數(shù)update()將地圖上待更新的選定年份作為其單一參數(shù)
為了更新地圖上某一年份對應(yīng)的數(shù)據(jù),我們需要執(zhí)行以下步驟:
- 為了給函數(shù)update()的參數(shù)給定年份,我們需要篩選數(shù)據(jù)
- 去掉地圖上不再需要的元素
- 在更新函數(shù)中添加更新前頁面未包含的新元素
filter data filter() → filter() #我們在d3中通過內(nèi)置篩選函數(shù)選定待繪年份后,進(jìn)行數(shù)據(jù)篩選
remove any elements →.exit() #為明確待刪除元素,我們需要在數(shù)據(jù)綁定后使用特殊的.exit() selection
add any new elements →.enter() #給選定的年份添加新元素時(shí),使用.enter()
此外,我還想添加一項(xiàng)新功能,顯示給定年份的世界杯參賽國
29. 采集參賽國
有關(guān) D3 中的集的文檔。
在本例中,由于根據(jù)年份篩選數(shù)據(jù)的步驟更為復(fù)雜,所以先找出選定年份的參賽國,只要知道如何選擇參賽國,接下來篩選年份也就不難了
找出選定年份的所有參賽國,要回到為實(shí)現(xiàn)數(shù)據(jù)嵌套而定義的聚合函數(shù)agg_year,該函數(shù)是選定年份舉行的所有賽事,在計(jì)算了總觀賽人數(shù)和待繪制坐標(biāo)以后,我們就可以將參賽球隊(duì)分組,最后以數(shù)組形式返回。
在本例中,我們要使用d3內(nèi)置的set()數(shù)據(jù)結(jié)構(gòu),它能夠聚集不同的對象,并且具有不重復(fù)添加已有數(shù)據(jù)的功能
首先將集合初始化為空,然后使用選定年份的參賽隊(duì)伍進(jìn)行迭代,依次添加
使用JavaScript內(nèi)置的forEach函數(shù)來調(diào)用leaves數(shù)組,forEach函數(shù)的功能和映射相似,但forEach找到的變量不以數(shù)組形式返回,它只執(zhí)行訪問函數(shù),并逐一傳遞數(shù)組的參數(shù)
在本例中,我們不會從forEach返回任何結(jié)果,只是將隊(duì)伍1和隊(duì)伍2添加到集合中
鑒于集合能夠自動刪除重復(fù)的數(shù)據(jù),我們就不必?fù)?dān)心球隊(duì)會被多次添加,集合會替我們把關(guān),這樣一來,選定年份的參賽球隊(duì)在集合中僅顯示一次
為了將球隊(duì)作為參數(shù)傳遞給返回對象,需要調(diào)用.values,這樣球隊(duì)集便將球隊(duì)名稱集合轉(zhuǎn)換為數(shù)組,在之后的編碼中操作更加簡單
現(xiàn)在我們有了選定年份的參賽國家,再回到update()函數(shù),將所有步驟串聯(lián)起來
30. 過濾
update()函數(shù)以每一年份為參數(shù),在此基礎(chǔ)上篩選數(shù)據(jù)
在本例中,我們篩選的其實(shí)是嵌套對象
根據(jù)年份將數(shù)據(jù)分組后,嵌套對象便會將key屬性設(shè)置為當(dāng)年的年份,然后再執(zhí)行篩選函數(shù),我們只需刪除key ,并將其與待篩選年份進(jìn)行對比
本例中,嵌套對象的keys實(shí)際為字符串,所以首先要將字符串轉(zhuǎn)換成日期,選出年份,再將其與update()函數(shù)的年份參數(shù)進(jìn)行對比
篩選函數(shù)的工作原理和映射相同,但是并不返回調(diào)用數(shù)組的所有元素,而只返回訪問函數(shù)中返回值為真的元素
在本例中,只有當(dāng)元素d的key等于update()函數(shù)年份參數(shù)時(shí),篩選函數(shù)返回值為真
在篩選出正確的數(shù)據(jù)后,我們就可以開始更新地圖和已繪制的圓形了,為此我們要用到數(shù)據(jù)綁定和enter()以及exit()
nest.key 文檔
31. 用一個(gè)關(guān)鍵函數(shù)連接數(shù)據(jù)
我們用數(shù)據(jù)綁定函數(shù)在地圖上添加圈圈
我們打算以動畫形式演示我們的地圖,并且不斷更新,因此需要非常明確,每個(gè)綁定的數(shù)據(jù)實(shí)際代表什么
svg.append('g')
.attr("class", "bubble")
.selectAll("circle")
.data(nested.sort(function(a, b){
return b.values['attendance'] - a.values['attendance'];
}),function(d) { #添加特殊的函數(shù)作為數(shù)據(jù)綁定的第二個(gè)參數(shù),d3將function(d)的返回值和前面選定的元素進(jìn)行綁定
return d['key']; #代表一個(gè)字符串,對應(yīng)世界杯的舉辦年份
})
.enter()
.append("circle")
.attr('cx', function(d) { return d.values['x']; })
.attr('cy', function(d) { return d.values['y']; })
.attr('r', function(d) {
return radius(d.values['attendance']);
})
.attr('fill', 'rgb(247, 148, 32)')
.attr('stroke', 'black')
.attr('stroke-width', 0.7)
.attr('opacity', 0.7);
我們可以采用簡單的方法,無需按照年份綁定數(shù)據(jù),可根據(jù)觀賽人數(shù)綁定數(shù)據(jù)
svg.append('g')
.attr("class", "bubble")
.selectAll("circle")
.data(nested.sort(function(a, b){
return b.values['attendance'] - a.values['attendance'];
}),function(d) { #添加特殊的函數(shù)作為數(shù)據(jù)綁定的第二個(gè)參數(shù),d3將function(d)的返回值和前面選定的元素進(jìn)行綁定
return d.values['attendance'];
})
.enter()
.append("circle")
.attr('cx', function(d) { return d.values['x']; })
.attr('cy', function(d) { return d.values['y']; })
.attr('r', function(d) {
return radius(d.values['attendance']);
})
.attr('fill', 'rgb(247, 148, 32)')
.attr('stroke', 'black')
.attr('stroke-width', 0.7)
.attr('opacity', 0.7);
32. 突出顯示的國家
使用 CSS 提取圓圈元素的樣式,在 HTML 文件頂部的樣式標(biāo)簽之間添加了以下代碼。
這種方法更干凈,更簡單
<style>
circle {
fill: orange;
stroke: black;
stroke-width: 0.7;
opacity: 0.7;
}
</style>
如果我們嘗試更新未舉行世界杯的某一年的國家,你認(rèn)為會發(fā)生什么?
● 更新函數(shù)過濾所有國家,最終使過濾部分為空,地圖上未出現(xiàn)圓圈
35. 更新函數(shù)小結(jié)
使用 D3.js 創(chuàng)建動畫和過渡(作者:Jerome Cukier)
學(xué)習(xí) D3:動畫與互動—第 3 部分(作者:Scott Becker)
D3 與 UI 動畫(作者:Andreas Koller)
更新函數(shù)的任務(wù):
指定年份,篩選數(shù)據(jù),更新數(shù)據(jù)綁定,移除因上次更新數(shù)據(jù)綁定造成的無關(guān)圓圈
36. 為每一年添加動畫效果
在動畫中,可以使用JavaScript的原生函數(shù)setTimeout,是在指定的毫秒數(shù)后,運(yùn)行函數(shù),要效果更好,還可以使用setInterval函數(shù),與之前函數(shù)的區(qū)別在于此函數(shù)可以循環(huán)運(yùn)行
我們這個(gè)例子中正好需要這種方式,來運(yùn)行更新函數(shù),一次顯示一屆世界杯舉辦年份,不斷循環(huán),想知道哪些年份要循環(huán),得使用數(shù)組,把世界杯所有舉辦年份放入數(shù)組
你可以從 JavaScript 基礎(chǔ)課程中重溫數(shù)組、for 循環(huán)和 if 語句。
你可能會考慮使用 array.push()。查閱有關(guān) MDN 的文檔。
記住,你要排除沒有舉行世界杯比賽的 1942 年和 1946 年。你可以使用帶有適當(dāng)條件的 if 語句來過濾年份。你可以使用 &&(表示“和”)或 ||(表示“或”)來檢查 if 語句中的多個(gè)條件。
function populate_years(start, end, step) {
var years = []; //empty years array
for(var year = start; year <= end; year += step) {
if(year !== 1942 && year !== 1946) {
years.push(year);
}
}
return years; //return years array
}
37.setInterval 和 clearInterval
setInterval
第一個(gè)參數(shù)是要運(yùn)行的函數(shù) 匿名函數(shù)
第一個(gè)參數(shù)運(yùn)行間隔指定的毫秒數(shù) 一秒
clearInterval
只有一個(gè)參數(shù)
就是setInterval 創(chuàng)建的間隔變量
39. 更新標(biāo)題
添加用來顯示賽事舉辦的年份,隨著年份改變,我們就能知道正在看的是哪一屆世界杯的數(shù)據(jù)
function update(year) {
var filtered = nested.filter(function(d) {
return new Date(d['key']).getUTCFullYear() === year;
});
d3.select('h2')
.text('world cup'+year);
40. 使用過渡來平滑化動畫
circles.enter()
.append("circle")
.transition() #過渡更流暢
.duration(500)
.attr('cx', function(d) { return d.values['x']; })
.attr('cy', function(d) { return d.values['y']; })
.attr('r', function(d) {
return radius(d.values['attendance']);
});
svg.selectAll('path')
.transition()
.duration(500)
.style('fill', update_countries)
.style('stroke', update_countries);
42. 增加交互性
對所有舉辦世界杯的年份添加按鈕,如果用戶點(diǎn)擊按鈕會跳到具體的年份并升級地圖,這樣就能夠根據(jù)世界杯舉辦的年份添加一些div元素
按鈕包含20個(gè)元素,與每一屆的世界杯相對應(yīng)
在 div 標(biāo)簽中添加按鈕,及為世界杯的每一年添加適當(dāng)?shù)奈谋緲?biāo)簽。
提示 1:要在 div 標(biāo)簽中創(chuàng)建按鈕,你需要在頁面(目前還不存在)上選擇 div 元素。你將在帶有 years_buttons 類的父 div 元素中創(chuàng)建這些 div 元素。
提示 2:你前期創(chuàng)建的 years 變量包含世界杯的年份。years 數(shù)組顯示為 [1930, 1934, ...]。
提示 3:帶有“year_buttons”類的 div 元素將包含所有按鈕的 div?,F(xiàn)在,假設(shè)你需要在 div 元素中添加 div 并將數(shù)據(jù)綁定到新的 div。使用以下常見的 D3 模式將數(shù)據(jù)綁定到頁面:
.selectAll()
.data()
.enter()
.append()提示 4:你需要使用 .text() 函數(shù)和匿名讀取函數(shù),向每個(gè)按鈕添加年份作為文本。匿名讀取函數(shù)比你之前見過的要簡單。思考你需要從年數(shù)組中訪問到什么。由于 .text() 的原因,你無需將年份的數(shù)據(jù)類型從 Integer 改為 String。
Jonathan 引用來樣式化按鈕的 CSS 代碼如下所示。你可以將此代碼添加到 HTML 文件頂部的樣式標(biāo)簽之間。
div.years_buttons {
position: fixed;
top: 5px;
left: 50px;
}
div.years_buttons div {
background-color: rgb(251, 201, 127);
padding: 3px;
margin: 7px;
}
var buttons = d3.select('body')
.append('div')
.attr('class','years_button').
.selectAll('div')
.data(years)
.enter()
.append('div')
.text(function(d) {
return d;
});
43. 延遲顯示按鈕
要確保放完按鈕的動態(tài)圖片之前,按鈕不會出現(xiàn)在頁面上
44. 向按鈕添加事件
語法是on函數(shù),第一個(gè)參數(shù)是你想要觸發(fā)回調(diào)函數(shù)的事件,第二個(gè)參數(shù)是你想運(yùn)行的函數(shù)
元素中出現(xiàn)指定事件時(shí),有時(shí)可能會采用事件處理程序,傳遞給事件處理程序的參數(shù)d與傳遞至d3中大部分訪問函數(shù)的參數(shù)d是一致的
d3訪問點(diǎn)擊事件的運(yùn)作方式
this大部分時(shí)候代表的是被點(diǎn)擊的元素本身
Javascript 的 'this'
如果你需要可以快速查閱的信息,此篇博文的部分內(nèi)容向你提供了簡潔明了的解釋。
如需深入研究 JavaScript 的關(guān)鍵詞 this,你可以就關(guān)鍵詞 this 學(xué)習(xí)面向?qū)ο蟮?JavaScript 課程!
Javascript 事件
D3.js 鼠標(biāo)事件(作者:Anthony Nosek)
鼠標(biāo)懸停、鼠標(biāo)移出、鼠標(biāo)按下教程(作者:Christophe Viau)
48.Matt 關(guān)于制作地圖的提示
地圖是一種特殊且難以表現(xiàn)的數(shù)據(jù),不過地圖的表現(xiàn)力也很強(qiáng)
制作地圖前很重要的一點(diǎn)是你想讓觀眾了解什么,以及你要如何來展示
注意刻度和變量的使用,確保觀眾能看出明確的結(jié)果
不論做什么圖表,提前思考都很重要,想清楚你想讓人們了解什么
作為一名數(shù)據(jù)科學(xué)家,你的工作是告訴人們他們需要了解什么,因此你可以采用取標(biāo)題、使用軸標(biāo)簽的方法來表達(dá)重要的內(nèi)容,還可以使用注釋來標(biāo)明特定事件或者異常數(shù)值
示例
溫度異常值是長期平均溫度的差值。(來源)
https://plot.ly/~MattSundquist/878/the-1000-most-populous-canadian-cities/
D3 資源
讓我們制作地圖(作者:Mike Bostock)
讓我們制作氣泡圖(作者:Mike Bostock)
如何在 D3 中制作面量圖(作者:EJ Fox)
其他資源
Python 中的工作草圖
視頻 2:13 處,Matt 提到用來制作地圖的 GUI。圖形用戶界面 (Graphical User Interface) 或 GUI 是一款點(diǎn)擊式軟件,是命令行界面的替代方案。Tableau 和 Data Wrapper 是 Matt 提到的兩款 GUI。
Tableau
Data Wrapper