1. 動(dòng)畫與互動(dòng)
在敘事結(jié)構(gòu)中全面應(yīng)用創(chuàng)意
D3如何幫你在可視化圖表中添加動(dòng)畫與互動(dòng)
用地理特征創(chuàng)建D3地圖
了解別人如何通過(guò)互動(dòng)在可視化圖表中添加額外的效果等恐,并完成引人入勝的故事
2. 案例研究:美國(guó)失業(yè)率
敘事精彩的交互式圖形范例:
人們的失業(yè)率
3. 交互性的好處
為什么交互性對(duì)數(shù)據(jù)可視化如此重要?
它可以幫助讀者了解比圖表中實(shí)際數(shù)據(jù)顯示的更多信息
如果有了懸浮文本彼宠、縮放搭独、切換等功能,那么觀眾可以進(jìn)一步研究圖表和數(shù)據(jù)
plot.ly 上的 Gapminder
Gapminder 世界
4.迭代過(guò)程
之間學(xué)習(xí)了:
敘事結(jié)構(gòu)菠红、給世界杯可視化圖添加背景
在世界杯可視化的例子中尚骄,我們了解到線型圖和散點(diǎn)圖不是傳遞數(shù)據(jù)信息的最佳方式簸喂,本課我們將進(jìn)一步迭代我們的圖形
在地圖上繪制數(shù)據(jù)毙死,給可視化圖表添加地理背景
通過(guò)D3動(dòng)畫燎潮,利用時(shí)間效果喻鳄,增強(qiáng)作者驅(qū)動(dòng)敘事,最后通過(guò)讓讀者交互探索圖表确封,更精細(xì)地檢測(cè)數(shù)據(jù)除呵,創(chuàng)建雞尾酒杯的杯口結(jié)構(gòu)
5. 讓我們制作地圖
添加背景過(guò)程的第一步是創(chuàng)建地圖,而D3擁有很好的地理能力
創(chuàng)建交互地圖:
- 獲取數(shù)據(jù)
(使用JSON格式編碼你需要展示的坐標(biāo))
- shapefile
二進(jìn)制編碼信息爪喘,人類無(wú)法讀取颜曾,需要使用特殊的程序解讀
有存儲(chǔ)限制,比如你在指定的形狀里添加屬性(名字或者其他數(shù)據(jù))的數(shù)量
大小也有限制秉剑,從而限制了地圖中坐標(biāo)的真實(shí)精確度 - GeoJSON
本課程中使用泛豪,這在大多數(shù)情況下都?jí)蛴昧?br> valid JSON/human readable/能方便地使用常見(jiàn)的開(kāi)發(fā)工具(文本編輯器、瀏覽器控制臺(tái))進(jìn)行探測(cè)和調(diào)試/數(shù)據(jù)文件更詳細(xì)更大侦鹏,在網(wǎng)絡(luò)上創(chuàng)建地圖诡曙,我們經(jīng)常需要通過(guò)網(wǎng)絡(luò)請(qǐng)求發(fā)送此信息,我們的圖表會(huì)同時(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)勢(shì)在于:
- can be parsed by most programming languages
- human readable
地圖學(xué)校
請(qǐng)注意跨释,地形(topography胸私,地面的高度或形狀)與拓?fù)浣Y(jié)構(gòu)(topology,在這種情況下與地點(diǎn)之間的鄰接關(guān)系和連接有關(guān))這兩個(gè)詞拼法很像鳖谈,但它們是不同的盖文。TopoJSON 對(duì)拓?fù)浣Y(jié)構(gòu)編碼,不對(duì)地形進(jìn)行編碼蚯姆。
7.什么是投影五续?
如果你想進(jìn)一步了解這些概念,mapschool.io 對(duì)地圖的諸多組成部分(拓?fù)浣Y(jié)構(gòu)龄恋、地理編碼疙驾、投影等)進(jìn)行了詳細(xì)的介紹。
用D3展示地圖和在散點(diǎn)圖中展示各點(diǎn)一樣
data domain → pixel range
data representation → screen representation 并在網(wǎng)頁(yè)上繪制
散點(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è)三維物體,我們沒(méi)辦法在二維平面展示,你能做的就是將三維物體用三維圖形表示出來(lái)扳肛,但這做起來(lái)通常比較復(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è)包含所有國(guó)家輪廓的GeoJSON文件
如果你想要可視化的地區(qū)沒(méi)有現(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)化為視覺(jué)表征电禀,geojson.io 是一款交互式的 GeoJSON 編輯器幢码。
10.檢查 GeoJSON
- 在var svg后加入debugger;
- 打開(kāi)web服務(wù)器
- 讓控制臺(tái)捕捉debugger
- 控制臺(tái)輸入geo_data進(jìn)行查看
使用 GeoJSON 的另一個(gè)好處是它只是個(gè)JSON文件,我們可以像其他文件一樣傳送
我們甚至可以用D3標(biāo)準(zhǔn)的JSON數(shù)據(jù)加載函數(shù)來(lái)加載它
GeoJSON的特點(diǎn)在于其形狀擴(kuò)展尖飞,通過(guò)查看geo_data,我們發(fā)現(xiàn)每個(gè)國(guó)家都有一個(gè)geometry key ,對(duì)應(yīng)一個(gè)既有坐標(biāo)又有類型的對(duì)象
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對(duì)象 繪制svg路徑來(lái)對(duì)地圖可視化
var map = svg.selectAll('path')
.data(geo_data.features) #features與國(guó)家坐標(biāo)的數(shù)組相對(duì)應(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)行上面的代碼政基,瀏覽器會(huì)出現(xiàn)一副地圖
但是有一些問(wèn)題:北半球頂部不完整贞铣,南極洲又非常大
為了更好地確定地圖的位置,我們可以在投影中使用scale()和transform(),通過(guò)這兩個(gè)函數(shù)腋么,我們可以移動(dòng)和操縱地圖的視覺(jué)化展示
scale():類似于谷歌地圖的放大和縮小功能
transform():類似將地圖的中心拖動(dòng)至不同的位置
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è)國(guó)家邊界的邊框線調(diào)整為深黑色線條咕娄,描邊的寬度稍細(xì)一些
.style('stroke-width',0.5)
在后面的代碼中,我們要下移地圖珊擂,將南極洲的那部分地圖切掉圣勒,因?yàn)槟蠘O洲沒(méi)有國(guó)家參與世界杯賽事
13.專題地圖
在地圖上加入背景信息,加入各年份世界杯的觀賽人數(shù)數(shù)據(jù)
用圓圈標(biāo)記世界杯的舉辦國(guó)摧扇,圓的半徑與該年的總觀賽人數(shù)成正比圣贸,這稱為主題地圖,指的是地圖中包含代表某個(gè)具體話題或者具體主題的數(shù)據(jù)
在我們的例子中扛稽,主題是世界杯吁峻,主題地圖的繪制,通常會(huì)通過(guò)在地圖上繪制一些數(shù)據(jù)添加一些額外的內(nèi)容
主題地圖的類型:
- dot maps
- Choropleth map 分級(jí)統(tǒng)計(jì)圖
根據(jù)區(qū)域?qū)Φ貓D標(biāo)色 - cartogram 變形地圖
根據(jù)數(shù)據(jù)值改變區(qū)域在张、形狀和尺寸 - 符號(hào)漸變地圖
是一個(gè)符號(hào)地圖 我們?cè)诘貓D上繪制符號(hào)(圓圈用含,漸變),符號(hào)的區(qū)域和半徑會(huì)根據(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ù)對(duì)象 - 傳遞至已定義好的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可無(wú)限嵌套,但使用太多的回調(diào)函數(shù)嵌套虽惭,不是一個(gè)好的做法
盡管我們?cè)诶碚撋峡梢詿o(wú)數(shù)次使用此方式來(lái)嵌套函數(shù)橡类,但最好還是要適度限制嵌套的次數(shù),以便簡(jiǎn)化邏輯芽唇,使代碼更加易懂顾画。
15. 嵌套函數(shù)
如需了解更多關(guān)于 D3 嵌套函數(shù)的信息,請(qǐng)查閱 D3 嵌套文檔和 D3 嵌套示例披摄。
繪制地圖的第二步亲雪,我們需要通過(guò)自世界杯開(kāi)賽以來(lái)舉辦的年份來(lái)給比賽分組勇凭,我們將比較世界杯前一年的觀賽人數(shù)和下一年的觀賽人數(shù)
d3在數(shù)據(jù)操作上的功能非常強(qiáng)大疚膊,為此我們可以使用d3的函數(shù)nest()來(lái)滿足我們的需求,而不是減少數(shù)據(jù)并在上面聚合
使用key()函數(shù)虾标,把它傳遞到訪問(wèn)器回調(diào)寓盗,無(wú)論這個(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)它是第一場(chǎng)比賽
debugger;
return d['date'].getUTCFullYear(); #審查d['date'].getUTCFullYear();得到1934
})
.rollup(function(leaves) { #傳遞給rollup()函數(shù)的是在key()函數(shù)指定的一個(gè)分組璧函,rollup()函數(shù)的任務(wù)是將17個(gè)對(duì)象/比賽提煉成一個(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ù)會(huì)轉(zhuǎn)換數(shù)組的每個(gè)元素蘸吓,再返回一個(gè)數(shù)據(jù)善炫,回調(diào)函數(shù)中傳遞的d代表leaves的每個(gè)元素,不論返回何值库继,都會(huì)存儲(chǔ)到返回?cái)?shù)組coords中
本例中箩艺,我們想將數(shù)據(jù)點(diǎn)的經(jīng)緯度對(duì)應(yīng)至某些像素值,這可以通過(guò)projection()函數(shù)得到宪萄,輸入經(jīng)緯度艺谆,得到像素x和y
像素到底有什么用?
如果某年有四個(gè)場(chǎng)館舉辦比賽拜英,那么我首先把每個(gè)場(chǎng)館的經(jīng)緯度轉(zhuǎn)化成x,y像素值静汤,最后我們要做的是計(jì)算所有場(chǎng)館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.檢查嵌套返回
聚合要做的最后一步虫给,是返回某些存儲(chǔ)在rollup()返回的最終結(jié)果中的對(duì)象
本例中,我們只返回'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)行視覺(jué)編碼時(shí)抹估,你應(yīng)該特別注意面積或體積所展現(xiàn)的數(shù)據(jù)。如果你不仔細(xì)處理數(shù)據(jù)及其視覺(jué)編碼舆床,你可能就會(huì)錯(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)在文章底部的文字。
更正:在此文章的早期版本中颊埃,圖形圓圈的大小沒(méi)有準(zhǔn)確地反映數(shù)據(jù)蔬充。
如果你不熟悉冰桶挑戰(zhàn),請(qǐng)查閱文章班利。你可能還從參加捐獻(xiàn)活動(dòng)的名人或你自己的家人那里看到過(guò) YouTube 視頻饥漫。
通過(guò)并排比較,你應(yīng)該注意到原始圖形中的圓圈要比 Vox 網(wǎng)站上更正圖形內(nèi)的圓圈大得多罗标。
讓我們借此學(xué)習(xí)機(jī)會(huì)庸队,理解如何使用圓圈面積準(zhǔn)確展現(xiàn)數(shù)據(jù)值。
原始圖形中的問(wèn)題是數(shù)據(jù)值被用于繪制圓圈的半徑闯割。如果你將數(shù)據(jù)值用于圓圈半徑彻消,那么圓圈面積將是半徑平方的三倍左右。
圓面積 = π*r2
或
圓面積 ≈ 3.14*r2
例如宙拉,數(shù)據(jù)值 4 將創(chuàng)建面積為 16π 的圓宾尚。數(shù)據(jù)值 5 將創(chuàng)建面積為 25π 的圓。
我們頗為高效地將數(shù)據(jù)值進(jìn)行平方谢澈,用來(lái)創(chuàng)建新的視覺(jué)表征煌贴。這造成圖形中圓圈的外觀要比其應(yīng)該展現(xiàn)的大得多。
要避免這個(gè)問(wèn)題澳化,數(shù)據(jù)值應(yīng)匹配圓圈的面積崔步。你可以將數(shù)據(jù)值開(kāi)平方,以確定每個(gè)圓圈的半徑缎谷。
Jonathan 將在后面的幾段視頻中解釋如何使用代碼來(lái)完成這一操作井濒。他將利用匿名讀取函數(shù)和 d3.scale.sqrt()
。
對(duì)于這道練習(xí)題列林,你應(yīng)該思考如何重新設(shè)計(jì)和改善圖形瑞你。請(qǐng)隨時(shí)在討論區(qū)中分享你的想法、示意圖或可視化圖形希痴。
如需了解圖形改善和重新設(shè)計(jì)的額外信息者甲,請(qǐng)?jiān)L問(wèn)以下相關(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') #對(duì)圓圈設(shè)置黑色描邊
.attr('stroke-width', 0.7) #描邊較細(xì)
.attr('opacity', 0.7); #增加透明度以便看清所有重疊的圓圈
對(duì)于多次舉辦世界杯的國(guó)家,圓圈可能出現(xiàn)重疊村象,但是小圓總是在上方,這是因?yàn)楹笃谑澜绫^賽人數(shù)增加
如果為了增強(qiáng)地圖效果而添加新數(shù)據(jù)醉者,要確保杜絕遮蔽現(xiàn)象
25.就繪圖順序?qū)?shù)據(jù)進(jìn)行排序
通過(guò)對(duì)數(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位于首位宰缤,可以說(shuō)a是本函數(shù)的第一個(gè)參數(shù)
返回值大于0颂翼,b位于首位,可以說(shuō)b是本函數(shù)的第二個(gè)參數(shù)
返回值等于0慨灭,則沒(méi)有位置變化朦乏,此時(shí)只要a和b保持原來(lái)的順序即可
總的來(lái)說(shuō)就是首先繪制人數(shù)多的
26. 我們?cè)谀膬?/h1>
我們?yōu)榱吮A艨臻g信息而犧牲了時(shí)間信息
我們知道,世界杯舉辦年份對(duì)觀賽人數(shù)的影響相當(dāng)大氧骤,因?yàn)殡S著時(shí)間推移呻疹,世界杯觀賽人數(shù)穩(wěn)步增加
地理和時(shí)間信息并得的方法是用動(dòng)畫表現(xiàn)時(shí)間過(guò)程
可將動(dòng)畫看作另一種視覺(jué)編碼,幫我們傳遞變化的時(shí)間數(shù)據(jù)
杯底:靜態(tài)圖语淘,是起點(diǎn)
杯莖:通過(guò)動(dòng)畫的作者驅(qū)動(dòng)敘述
杯口:通過(guò)互動(dòng)/交互的讀者驅(qū)動(dòng)敘述
27. 更新函數(shù)
要用動(dòng)畫呈現(xiàn)世界杯年份诲宇,需要做兩件事:
- 使用函數(shù)更新地圖
因?yàn)槲覀儠?huì)對(duì)歷年數(shù)據(jù)反復(fù)調(diào)用更新际歼,我們要把數(shù)據(jù)封裝到一個(gè)能讓我們隨時(shí)調(diào)用的函數(shù)中 - 對(duì)世界杯年份進(jìn)行循環(huán)惶翻,并將年份傳遞給更新函數(shù)
28. 概述更新函數(shù)
函數(shù)update()將地圖上待更新的選定年份作為其單一參數(shù)
為了更新地圖上某一年份對(duì)應(yīng)的數(shù)據(jù),我們需要執(zhí)行以下步驟:
- 為了給函數(shù)update()的參數(shù)給定年份鹅心,我們需要篩選數(shù)據(jù)
- 去掉地圖上不再需要的元素
- 在更新函數(shù)中添加更新前頁(yè)面未包含的新元素
filter data filter() → filter() #我們?cè)赿3中通過(guò)內(nèi)置篩選函數(shù)選定待繪年份后吕粗,進(jìn)行數(shù)據(jù)篩選
remove any elements →.exit() #為明確待刪除元素,我們需要在數(shù)據(jù)綁定后使用特殊的.exit() selection
add any new elements →.enter() #給選定的年份添加新元素時(shí)旭愧,使用.enter()
此外颅筋,我還想添加一項(xiàng)新功能,顯示給定年份的世界杯參賽國(guó)
29. 采集參賽國(guó)
有關(guān) D3 中的集的文檔输枯。
在本例中议泵,由于根據(jù)年份篩選數(shù)據(jù)的步驟更為復(fù)雜,所以先找出選定年份的參賽國(guó)桃熄,只要知道如何選擇參賽國(guó)先口,接下來(lái)篩選年份也就不難了
找出選定年份的所有參賽國(guó),要回到為實(shí)現(xiàn)數(shù)據(jù)嵌套而定義的聚合函數(shù)agg_year,該函數(shù)是選定年份舉行的所有賽事瞳收,在計(jì)算了總觀賽人數(shù)和待繪制坐標(biāo)以后碉京,我們就可以將參賽球隊(duì)分組,最后以數(shù)組形式返回螟深。
在本例中谐宙,我們要使用d3內(nèi)置的set()數(shù)據(jù)結(jié)構(gòu),它能夠聚集不同的對(duì)象界弧,并且具有不重復(fù)添加已有數(shù)據(jù)的功能
首先將集合初始化為空凡蜻,然后使用選定年份的參賽隊(duì)伍進(jìn)行迭代搭综,依次添加
使用JavaScript內(nèi)置的forEach函數(shù)來(lái)調(diào)用leaves數(shù)組,forEach函數(shù)的功能和映射相似划栓,但forEach找到的變量不以數(shù)組形式返回设凹,它只執(zhí)行訪問(wèn)函數(shù),并逐一傳遞數(shù)組的參數(shù)
在本例中茅姜,我們不會(huì)從forEach返回任何結(jié)果闪朱,只是將隊(duì)伍1和隊(duì)伍2添加到集合中
鑒于集合能夠自動(dòng)刪除重復(fù)的數(shù)據(jù),我們就不必?fù)?dān)心球隊(duì)會(huì)被多次添加钻洒,集合會(huì)替我們把關(guān)奋姿,這樣一來(lái),選定年份的參賽球隊(duì)在集合中僅顯示一次
為了將球隊(duì)作為參數(shù)傳遞給返回對(duì)象素标,需要調(diào)用.values,這樣球隊(duì)集便將球隊(duì)名稱集合轉(zhuǎn)換為數(shù)組称诗,在之后的編碼中操作更加簡(jiǎn)單
現(xiàn)在我們有了選定年份的參賽國(guó)家,再回到update()函數(shù)头遭,將所有步驟串聯(lián)起來(lái)
30. 過(guò)濾
update()函數(shù)以每一年份為參數(shù)寓免,在此基礎(chǔ)上篩選數(shù)據(jù)
在本例中,我們篩選的其實(shí)是嵌套對(duì)象
根據(jù)年份將數(shù)據(jù)分組后计维,嵌套對(duì)象便會(huì)將key屬性設(shè)置為當(dāng)年的年份袜香,然后再執(zhí)行篩選函數(shù),我們只需刪除key ,并將其與待篩選年份進(jìn)行對(duì)比
本例中鲫惶,嵌套對(duì)象的keys實(shí)際為字符串蜈首,所以首先要將字符串轉(zhuǎn)換成日期,選出年份欠母,再將其與update()函數(shù)的年份參數(shù)進(jìn)行對(duì)比
篩選函數(shù)的工作原理和映射相同欢策,但是并不返回調(diào)用數(shù)組的所有元素,而只返回訪問(wèn)函數(shù)中返回值為真的元素
在本例中赏淌,只有當(dāng)元素d的key等于update()函數(shù)年份參數(shù)時(shí)踩寇,篩選函數(shù)返回值為真
在篩選出正確的數(shù)據(jù)后,我們就可以開(kāi)始更新地圖和已繪制的圓形了六水,為此我們要用到數(shù)據(jù)綁定和enter()以及exit()
nest.key 文檔
31. 用一個(gè)關(guān)鍵函數(shù)連接數(shù)據(jù)
我們用數(shù)據(jù)綁定函數(shù)在地圖上添加圈圈
我們打算以動(dòng)畫形式演示我們的地圖俺孙,并且不斷更新,因此需要非常明確缩擂,每個(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è)字符串,對(duì)應(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);
我們可以采用簡(jiǎn)單的方法胯盯,無(wú)需按照年份綁定數(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. 突出顯示的國(guó)家
使用 CSS 提取圓圈元素的樣式,在 HTML 文件頂部的樣式標(biāo)簽之間添加了以下代碼博脑。
這種方法更干凈憎乙,更簡(jiǎn)單
<style>
circle {
fill: orange;
stroke: black;
stroke-width: 0.7;
opacity: 0.7;
}
</style>
如果我們嘗試更新未舉行世界杯的某一年的國(guó)家票罐,你認(rèn)為會(huì)發(fā)生什么?
● 更新函數(shù)過(guò)濾所有國(guó)家泞边,最終使過(guò)濾部分為空该押,地圖上未出現(xiàn)圓圈
35. 更新函數(shù)小結(jié)
使用 D3.js 創(chuàng)建動(dòng)畫和過(guò)渡(作者:Jerome Cukier)
學(xué)習(xí) D3:動(dòng)畫與互動(dòng)—第 3 部分(作者:Scott Becker)
D3 與 UI 動(dòng)畫(作者:Andreas Koller)
更新函數(shù)的任務(wù):
指定年份,篩選數(shù)據(jù)阵谚,更新數(shù)據(jù)綁定蚕礼,移除因上次更新數(shù)據(jù)綁定造成的無(wú)關(guān)圓圈
36. 為每一年添加動(dòng)畫效果
在動(dòng)畫中,可以使用JavaScript的原生函數(shù)setTimeout,是在指定的毫秒數(shù)后梢什,運(yùn)行函數(shù)奠蹬,要效果更好,還可以使用setInterval函數(shù)嗡午,與之前函數(shù)的區(qū)別在于此函數(shù)可以循環(huán)運(yùn)行
我們這個(gè)例子中正好需要這種方式囤躁,來(lái)運(yùn)行更新函數(shù),一次顯示一屆世界杯舉辦年份荔睹,不斷循環(huán)狸演,想知道哪些年份要循環(huán),得使用數(shù)組僻他,把世界杯所有舉辦年份放入數(shù)組
你可以從 JavaScript 基礎(chǔ)課程中重溫數(shù)組宵距、for 循環(huán)和 if 語(yǔ)句。
你可能會(huì)考慮使用 array.push()
中姜。查閱有關(guān) MDN 的文檔消玄。
記住,你要排除沒(méi)有舉行世界杯比賽的 1942 年和 1946 年丢胚。你可以使用帶有適當(dāng)條件的 if 語(yǔ)句來(lái)過(guò)濾年份。你可以使用 &&
(表示“和”)或 ||
(表示“或”)來(lái)檢查 if 語(yǔ)句中的多個(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)建的間隔變量
動(dòng)態(tài)更新 D3 數(shù)據(jù)
39. 更新標(biāo)題
添加用來(lái)顯示賽事舉辦的年份携龟,隨著年份改變,我們就能知道正在看的是哪一屆世界杯的數(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. 使用過(guò)渡來(lái)平滑化動(dòng)畫
circles.enter()
.append("circle")
.transition() #過(guò)渡更流暢
.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. 增加交互性
對(duì)所有舉辦世界杯的年份添加按鈕勘高,如果用戶點(diǎn)擊按鈕會(huì)跳到具體的年份并升級(jí)地圖峡蟋,這樣就能夠根據(jù)世界杯舉辦的年份添加一些div元素
按鈕包含20個(gè)元素,與每一屆的世界杯相對(duì)應(yīng)
在 div 標(biāo)簽中添加按鈕华望,及為世界杯的每一年添加適當(dāng)?shù)奈谋緲?biāo)簽蕊蝗。
提示 1:要在 div 標(biāo)簽中創(chuàng)建按鈕,你需要在頁(yè)面(目前還不存在)上選擇 div 元素赖舟。你將在帶有 years_buttons 類的父 div 元素中創(chuàng)建這些 div 元素蓬戚。
提示 2:你前期創(chuàng)建的 years 變量包含世界杯的年份。years 數(shù)組顯示為 [1930, 1934, ...]宾抓。
提示 3:帶有“year_buttons”類的 div 元素將包含所有按鈕的 div∽愉觯現(xiàn)在豫喧,假設(shè)你需要在 div 元素中添加 div 并將數(shù)據(jù)綁定到新的 div。使用以下常見(jiàn)的 D3 模式將數(shù)據(jù)綁定到頁(yè)面:
.selectAll()
.data()
.enter()
.append()提示 4:你需要使用 .text() 函數(shù)和匿名讀取函數(shù)幢泼,向每個(gè)按鈕添加年份作為文本紧显。匿名讀取函數(shù)比你之前見(jiàn)過(guò)的要簡(jiǎn)單。思考你需要從年數(shù)組中訪問(wèn)到什么缕棵。由于 .text() 的原因孵班,你無(wú)需將年份的數(shù)據(jù)類型從 Integer 改為 String。
Jonathan 引用來(lái)樣式化按鈕的 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. 延遲顯示按鈕
要確保放完按鈕的動(dòng)態(tài)圖片之前忽匈,按鈕不會(huì)出現(xiàn)在頁(yè)面上
44. 向按鈕添加事件
語(yǔ)法是on函數(shù)房午,第一個(gè)參數(shù)是你想要觸發(fā)回調(diào)函數(shù)的事件,第二個(gè)參數(shù)是你想運(yùn)行的函數(shù)
元素中出現(xiàn)指定事件時(shí)丹允,有時(shí)可能會(huì)采用事件處理程序郭厌,傳遞給事件處理程序的參數(shù)d與傳遞至d3中大部分訪問(wèn)函數(shù)的參數(shù)d是一致的
d3訪問(wèn)點(diǎn)擊事件的運(yùn)作方式
this大部分時(shí)候代表的是被點(diǎn)擊的元素本身
Javascript 的 'this'
如果你需要可以快速查閱的信息,此篇博文的部分內(nèi)容向你提供了簡(jiǎn)潔明了的解釋雕蔽。
如需深入研究 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ù),不過(guò)地圖的表現(xiàn)力也很強(qiáng)
制作地圖前很重要的一點(diǎn)是你想讓觀眾了解什么嚣艇,以及你要如何來(lái)展示
注意刻度和變量的使用承冰,確保觀眾能看出明確的結(jié)果
不論做什么圖表,提前思考都很重要食零,想清楚你想讓人們了解什么
作為一名數(shù)據(jù)科學(xué)家困乒,你的工作是告訴人們他們需要了解什么,因此你可以采用取標(biāo)題贰谣、使用軸標(biāo)簽的方法來(lái)表達(dá)重要的內(nèi)容娜搂,還可以使用注釋來(lái)標(biāo)明特定事件或者異常數(shù)值
示例
溫度異常值是長(zhǎng)期平均溫度的差值。(來(lái)源)
https://plot.ly/~MattSundquist/878/the-1000-most-populous-canadian-cities/
D3 資源
讓我們制作地圖(作者:Mike Bostock)
讓我們制作氣泡圖(作者:Mike Bostock)
如何在 D3 中制作面量圖(作者:EJ Fox)
其他資源
Python 中的工作草圖
視頻 2:13 處吱抚,Matt 提到用來(lái)制作地圖的 GUI百宇。圖形用戶界面 (Graphical User Interface) 或 GUI 是一款點(diǎn)擊式軟件,是命令行界面的替代方案秘豹。Tableau 和 Data Wrapper 是 Matt 提到的兩款 GUI携御。
Tableau
Data Wrapper