動畫與互動

1. 動畫與互動

在敘事結構中全面應用創意
D3如何幫你在可視化圖表中添加動畫與互動
用地理特征創建D3地圖
了解別人如何通過互動在可視化圖表中添加額外的效果,并完成引人入勝的故事

2. 案例研究:美國失業率

敘事精彩的交互式圖形范例:
人們的失業率

3. 交互性的好處

為什么交互性對數據可視化如此重要?
它可以幫助讀者了解比圖表中實際數據顯示的更多信息
如果有了懸浮文本、縮放、切換等功能,那么觀眾可以進一步研究圖表和數據
plot.ly 上的 Gapminder
Gapminder 世界

4.迭代過程

之間學習了:
敘事結構、給世界杯可視化圖添加背景
在世界杯可視化的例子中,我們了解到線型圖和散點圖不是傳遞數據信息的最佳方式,本課我們將進一步迭代我們的圖形

在地圖上繪制數據,給可視化圖表添加地理背景
通過D3動畫,利用時間效果,增強作者驅動敘事,最后通過讓讀者交互探索圖表,更精細地檢測數據,創建雞尾酒杯的杯口結構

5. 讓我們制作地圖

添加背景過程的第一步是創建地圖,而D3擁有很好的地理能力
創建交互地圖:

  1. 獲取數據
    (使用JSON格式編碼你需要展示的坐標)
  • shapefile
    二進制編碼信息,人類無法讀取,需要使用特殊的程序解讀
    有存儲限制,比如你在指定的形狀里添加屬性(名字或者其他數據)的數量
    大小也有限制,從而限制了地圖中坐標的真實精確度
  • GeoJSON
    本課程中使用,這在大多數情況下都夠用了
    valid JSON/human readable/能方便地使用常見的開發工具(文本編輯器、瀏覽器控制臺)進行探測和調試/數據文件更詳細更大,在網絡上創建地圖,我們經常需要通過網絡請求發送此信息,我們的圖表會同時給瀏覽器和服務器施壓,并增加加載延遲,
  • TopoJSON
    GeoJSON的擴展,原始文件實際上比shapefile和GeoJSON要小
    可以編碼拓撲結構,但是GeoJSON不能
  1. 繪制數據,即世界杯觀賽人數數據

6. GeoJSON 與 Shapefile

與Shapefile相比,GeoJSON 的優勢在于:

  • can be parsed by most programming languages
  • human readable

地圖學校
請注意,地形(topography,地面的高度或形狀)與拓撲結構(topology,在這種情況下與地點之間的鄰接關系和連接有關)這兩個詞拼法很像,但它們是不同的。TopoJSON 對拓撲結構編碼,不對地形進行編碼。

7.什么是投影?

如果你想進一步了解這些概念,mapschool.io 對地圖的諸多組成部分(拓撲結構、地理編碼、投影等)進行了詳細的介紹。

用D3展示地圖和在散點圖中展示各點一樣
data domain → pixel range
data representation → screen representation 并在網頁上繪制

散點圖中:
日期表示年份
浮點數表示觀賽人數
這兩者都要轉換為像素值,即用圖表中x坐標和y坐標
scale()

D3中展示地圖:
地理坐標 → 像素值域
geographic → pixel range
經度坐標(x軸)/緯度坐標(y軸) → 像素
latitude/longitude/ → pixels
mercator()

坐標數據代表球形上的點,實際上要在三維中編碼信息
由于地球是一個三維物體,我們沒辦法在二維平面展示,你能做的就是將三維物體用三維圖形表示出來,但這做起來通常比較復雜,你只能看到地球的一面

另一種方法是投影Projection
切割球體的表面,試著壓成平面
這是一種在更低維度上展示更高維度的東西而不丟失信息或失真的方法

mercator投影法 扭曲人口最少的地區,即兩極附近的地區

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 一個包含所有國家輪廓的GeoJSON文件
如果你想要可視化的地區沒有現成的GeoJSON文件,可以使用工具將shape files 轉換成GeoJSON

在你可以下載的代碼文件中,widthheight 值略有不同。你可以在課程中調整這些值,看看可視化圖形將如何變化。

Ogre:將空間文件轉換為 GeoJSON

如何將形狀文件轉換為可在 Github 上使用的 GeoJSON(作者:Ben Balter)

如果你想了解 GeoJSON 值如何轉化為視覺表征,geojson.io 是一款交互式的 GeoJSON 編輯器。

10.檢查 GeoJSON

  • 在var svg后加入debugger;
  • 打開web服務器
  • 讓控制臺捕捉debugger
  • 控制臺輸入geo_data進行查看
    使用 GeoJSON 的另一個好處是它只是個JSON文件,我們可以像其他文件一樣傳送
    我們甚至可以用D3標準的JSON數據加載函數來加載它
    GeoJSON的特點在于其形狀擴展,通過查看geo_data,我們發現每個國家都有一個geometry key ,對應一個既有坐標又有類型的對象

11.從 SVG 路徑繪制地圖

var projection  = d3.geo.mercator();  #類似于我們為圖表設置尺寸,我用scale將一個值/整數/浮點數轉換成像素點
var path = d3.geo.path().projection(projection) #構建svg對象 繪制svg路徑來對地圖可視化
var map = svg.selectAll('path')     
             .data(geo_data.features) #features與國家坐標的數組相對應
             .enter()
             .append('path')    #svg路徑元素非常靈活,能夠代表大多數形狀
             .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. 繪制并更改地圖

運行上面的代碼,瀏覽器會出現一副地圖
但是有一些問題:北半球頂部不完整,南極洲又非常大
為了更好地確定地圖的位置,我們可以在投影中使用scale()和transform(),通過這兩個函數,我們可以移動和操縱地圖的視覺化展示
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)') #將地圖的填充色從黑色變為藍色
                       .style('stroke','black') #將每個國家邊界的邊框線調整為深黑色線條,描邊的寬度稍細一些
                       .style('stroke-width',0.5)

在后面的代碼中,我們要下移地圖,將南極洲的那部分地圖切掉,因為南極洲沒有國家參與世界杯賽事

13.專題地圖

在地圖上加入背景信息,加入各年份世界杯的觀賽人數數據
用圓圈標記世界杯的舉辦國,圓的半徑與該年的總觀賽人數成正比,這稱為主題地圖,指的是地圖中包含代表某個具體話題或者具體主題的數據
在我們的例子中,主題是世界杯,主題地圖的繪制,通常會通過在地圖上繪制一些數據添加一些額外的內容
主題地圖的類型:

  • dot maps
  • Choropleth map 分級統計圖
    根據區域對地圖標色
  • cartogram 變形地圖
    根據數據值改變區域、形狀和尺寸
  • 符號漸變地圖
    是一個符號地圖 我們在地圖上繪制符號(圓圈,漸變),符號的區域和半徑會根據它們代表的數據而變化

14. 使用嵌套函數加載數據

在地圖上繪制代表觀賽人數的圓圈

  1. 載入觀賽人數數據
    使用中間數據轉換函數把觀賽人數轉換成一個整數,把日期轉換成JavaScript數據對象
  2. 傳遞至已定義好的plot_points函數
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);

總結:
調用d3.json將world_countries.json載入到draw函數中,在draw函數里面調用d3.tsv,異步載入世界杯觀賽人數數據,文件載入完畢后傳到plot_points函數。理論上,如果我們要載入更多數據,我們可再次調用plot_points內的d3.json,d3.tsv可無限嵌套,但使用太多的回調函數嵌套,不是一個好的做法
盡管我們在理論上可以無數次使用此方式來嵌套函數,但最好還是要適度限制嵌套的次數,以便簡化邏輯,使代碼更加易懂。

15. 嵌套函數

如需了解更多關于 D3 嵌套函數的信息,請查閱 D3 嵌套文檔D3 嵌套示例

繪制地圖的第二步,我們需要通過自世界杯開賽以來舉辦的年份來給比賽分組,我們將比較世界杯前一年的觀賽人數和下一年的觀賽人數
d3在數據操作上的功能非常強大,為此我們可以使用d3的函數nest()來滿足我們的需求,而不是減少數據并在上面聚合

使用key()函數,把它傳遞到訪問器回調,無論這個回調返回什么都是嵌套分組的值
一旦你用某種方式給數據分組完畢,你需要以一些有意義的方式將其聚合

function plot_points(data) {
    var nested = d3.nest()
                   .key(function(d) {
                   })
                   .rollup(function(leaves) {
                   })
                   .entries(data);
};

16.聚合數據

function plot_points(data) {  #用console.table(data.slice(0,10))檢查數據
            //draw circles logic
    debugger;
    var nested = d3.nest()
                   .key(function(d) {  #審查d,發現它是第一場比賽
                      debugger;
                      return d['date'].getUTCFullYear(); #審查d['date'].getUTCFullYear();得到1934
                   })
                   .rollup(function(leaves) {  #傳遞給rollup()函數的是在key()函數指定的一個分組,rollup()函數的任務是將17個對象/比賽提煉成一個值,或者是一個聚合
                      debugger;
                      return "";
                   })
                   .entries(data);
        };

完成rollup()函數,每一組需要三個東西:

  1. 每一年比賽的總觀賽人數 使用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'];
                      });
  1. 地圖上圓圈的經度
  2. 地圖上圓圈的緯度

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()函數會轉換數組的每個元素,再返回一個數據,回調函數中傳遞的d代表leaves的每個元素,不論返回何值,都會存儲到返回數組coords中

本例中,我們想將數據點的經緯度對應至某些像素值,這可以通過projection()函數得到,輸入經緯度,得到像素x和y

像素到底有什么用?
如果某年有四個場館舉辦比賽,那么我首先把每個場館的經緯度轉化成x,y像素值,最后我們要做的是計算所有場館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()返回的最終結果中的對象
本例中,我們只返回'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.向地圖添加圓圈

圓圈半徑表示該年的觀賽人數

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. 確定觀賽人數圓圈的大小

設置合適的圓圈比例,讓其正確反映觀賽人數

22. 如何用圓圈撒謊

在使用圓圈或其他任意形狀進行視覺編碼時,你應該特別注意面積或體積所展現的數據。如果你不仔細處理數據及其視覺編碼,你可能就會錯誤地展現數據,報告夸大的發現結果,更糟糕的也許是失去讀者的信任。

以這兩個圖形為例。原始設計刊登在 Vox Media 撰寫的文章《關于冰桶挑戰的真相》中,并且圖形的更正版本在之后也得到發布。這篇文章現在呈現的是更正版本,原始圖形是這個

注意:以下為出現在文章底部的文字。

更正:在此文章的早期版本中,圖形圓圈的大小沒有準確地反映數據。

如果你不熟悉冰桶挑戰,請查閱文章。你可能還從參加捐獻活動的名人或你自己的家人那里看到過 YouTube 視頻。

通過并排比較,你應該注意到原始圖形中的圓圈要比 Vox 網站上更正圖形內的圓圈大得多。

讓我們借此學習機會,理解如何使用圓圈面積準確展現數據值。

原始圖形中的問題是數據值被用于繪制圓圈的半徑。如果你將數據值用于圓圈半徑,那么圓圈面積將是半徑平方的三倍左右。

圓面積 = π*r2

圓面積 ≈ 3.14*r2

例如,數據值 4 將創建面積為 16π 的圓。數據值 5 將創建面積為 25π 的圓。

我們頗為高效地將數據值進行平方,用來創建新的視覺表征。這造成圖形中圓圈的外觀要比其應該展現的大得多。

要避免這個問題,數據值應匹配圓圈的面積。你可以將數據值開平方,以確定每個圓圈的半徑。

Jonathan 將在后面的幾段視頻中解釋如何使用代碼來完成這一操作。他將利用匿名讀取函數和 d3.scale.sqrt()

對于這道練習題,你應該思考如何重新設計和改善圖形。請隨時在討論區中分享你的想法、示意圖或可視化圖形。

如需了解圖形改善和重新設計的額外信息,請訪問以下相關閱讀鏈接。

相關閱讀

虛假可視化:記者把可視化圖弄錯了(作者:Randy Krum)

惱人的氣泡圖(作者:David Mendoza)

23. 半徑標尺

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. 調整地圖設計

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') #對圓圈設置黑色描邊
               .attr('stroke-width', 0.7) #描邊較細
               .attr('opacity', 0.7); #增加透明度以便看清所有重疊的圓圈

對于多次舉辦世界杯的國家,圓圈可能出現重疊,但是小圓總是在上方,這是因為后期世界杯觀賽人數增加
如果為了增強地圖效果而添加新數據,要確保杜絕遮蔽現象

25.就繪圖順序對數據進行排序

通過對數據分類,可以避免遮蔽現象
查看有關 JavaScript 排序函數的文檔。

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是本函數的第一個參數
返回值大于0,b位于首位,可以說b是本函數的第二個參數
返回值等于0,則沒有位置變化,此時只要a和b保持原來的順序即可
總的來說就是首先繪制人數多的

26. 我們在哪兒

我們為了保留空間信息而犧牲了時間信息
我們知道,世界杯舉辦年份對觀賽人數的影響相當大,因為隨著時間推移,世界杯觀賽人數穩步增加
地理和時間信息并得的方法是用動畫表現時間過程
可將動畫看作另一種視覺編碼,幫我們傳遞變化的時間數據


image.png

杯底:靜態圖,是起點
杯莖:通過動畫的作者驅動敘述
杯口:通過互動/交互的讀者驅動敘述

27. 更新函數

要用動畫呈現世界杯年份,需要做兩件事:

  • 使用函數更新地圖
    因為我們會對歷年數據反復調用更新,我們要把數據封裝到一個能讓我們隨時調用的函數中
  • 對世界杯年份進行循環,并將年份傳遞給更新函數

28. 概述更新函數

函數update()將地圖上待更新的選定年份作為其單一參數
為了更新地圖上某一年份對應的數據,我們需要執行以下步驟:

  1. 為了給函數update()的參數給定年份,我們需要篩選數據
  2. 去掉地圖上不再需要的元素
  3. 在更新函數中添加更新前頁面未包含的新元素
filter data filter() → filter() #我們在d3中通過內置篩選函數選定待繪年份后,進行數據篩選
remove any elements →.exit() #為明確待刪除元素,我們需要在數據綁定后使用特殊的.exit() selection 
add any new elements →.enter() #給選定的年份添加新元素時,使用.enter()

此外,我還想添加一項新功能,顯示給定年份的世界杯參賽國

29. 采集參賽國

有關 D3 中的集的文檔。

在本例中,由于根據年份篩選數據的步驟更為復雜,所以先找出選定年份的參賽國,只要知道如何選擇參賽國,接下來篩選年份也就不難了
找出選定年份的所有參賽國,要回到為實現數據嵌套而定義的聚合函數agg_year,該函數是選定年份舉行的所有賽事,在計算了總觀賽人數和待繪制坐標以后,我們就可以將參賽球隊分組,最后以數組形式返回。
在本例中,我們要使用d3內置的set()數據結構,它能夠聚集不同的對象,并且具有不重復添加已有數據的功能

首先將集合初始化為空,然后使用選定年份的參賽隊伍進行迭代,依次添加
使用JavaScript內置的forEach函數來調用leaves數組,forEach函數的功能和映射相似,但forEach找到的變量不以數組形式返回,它只執行訪問函數,并逐一傳遞數組的參數
在本例中,我們不會從forEach返回任何結果,只是將隊伍1和隊伍2添加到集合中
鑒于集合能夠自動刪除重復的數據,我們就不必擔心球隊會被多次添加,集合會替我們把關,這樣一來,選定年份的參賽球隊在集合中僅顯示一次
為了將球隊作為參數傳遞給返回對象,需要調用.values,這樣球隊集便將球隊名稱集合轉換為數組,在之后的編碼中操作更加簡單
現在我們有了選定年份的參賽國家,再回到update()函數,將所有步驟串聯起來

30. 過濾

update()函數以每一年份為參數,在此基礎上篩選數據
在本例中,我們篩選的其實是嵌套對象
根據年份將數據分組后,嵌套對象便會將key屬性設置為當年的年份,然后再執行篩選函數,我們只需刪除key ,并將其與待篩選年份進行對比
本例中,嵌套對象的keys實際為字符串,所以首先要將字符串轉換成日期,選出年份,再將其與update()函數的年份參數進行對比
篩選函數的工作原理和映射相同,但是并不返回調用數組的所有元素,而只返回訪問函數中返回值為真的元素
在本例中,只有當元素d的key等于update()函數年份參數時,篩選函數返回值為真
在篩選出正確的數據后,我們就可以開始更新地圖和已繪制的圓形了,為此我們要用到數據綁定和enter()以及exit()
nest.key 文檔

31. 用一個關鍵函數連接數據

我們用數據綁定函數在地圖上添加圈圈
我們打算以動畫形式演示我們的地圖,并且不斷更新,因此需要非常明確,每個綁定的數據實際代表什么

svg.append('g')
               .attr("class", "bubble")
               .selectAll("circle")
               .data(nested.sort(function(a, b){ 
                  return b.values['attendance'] - a.values['attendance'];
               }),function(d) {      #添加特殊的函數作為數據綁定的第二個參數,d3將function(d)的返回值和前面選定的元素進行綁定
                  return d['key']; #代表一個字符串,對應世界杯的舉辦年份
               })
               .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);

我們可以采用簡單的方法,無需按照年份綁定數據,可根據觀賽人數綁定數據

svg.append('g')
               .attr("class", "bubble")
               .selectAll("circle")
               .data(nested.sort(function(a, b){ 
                  return b.values['attendance'] - a.values['attendance'];
               }),function(d) {      #添加特殊的函數作為數據綁定的第二個參數,d3將function(d)的返回值和前面選定的元素進行綁定
                  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 文件頂部的樣式標簽之間添加了以下代碼。
這種方法更干凈,更簡單

<style>
    circle {
        fill: orange;
        stroke: black;
        stroke-width: 0.7;
        opacity: 0.7;
    }
</style>

如果我們嘗試更新未舉行世界杯的某一年的國家,你認為會發生什么?
● 更新函數過濾所有國家,最終使過濾部分為空,地圖上未出現圓圈

35. 更新函數小結

使用 D3.js 創建動畫和過渡(作者:Jerome Cukier)

學習 D3:動畫與互動—第 3 部分(作者:Scott Becker)

D3 與 UI 動畫(作者:Andreas Koller)

更新函數的任務:
指定年份,篩選數據,更新數據綁定,移除因上次更新數據綁定造成的無關圓圈

36. 為每一年添加動畫效果

在動畫中,可以使用JavaScript的原生函數setTimeout,是在指定的毫秒數后,運行函數,要效果更好,還可以使用setInterval函數,與之前函數的區別在于此函數可以循環運行
我們這個例子中正好需要這種方式,來運行更新函數,一次顯示一屆世界杯舉辦年份,不斷循環,想知道哪些年份要循環,得使用數組,把世界杯所有舉辦年份放入數組

你可以從 JavaScript 基礎課程中重溫數組for 循環if 語句

你可能會考慮使用 array.push()。查閱有關 MDN 的文檔

記住,你要排除沒有舉行世界杯比賽的 1942 年和 1946 年。你可以使用帶有適當條件的 if 語句來過濾年份。你可以使用 &&(表示“和”)或 ||(表示“或”)來檢查 if 語句中的多個條件。

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
第一個參數是要運行的函數 匿名函數
第一個參數運行間隔指定的毫秒數 一秒

clearInterval
只有一個參數
就是setInterval 創建的間隔變量

動態更新 D3 數據

39. 更新標題

添加用來顯示賽事舉辦的年份,隨著年份改變,我們就能知道正在看的是哪一屆世界杯的數據

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. 增加交互性

對所有舉辦世界杯的年份添加按鈕,如果用戶點擊按鈕會跳到具體的年份并升級地圖,這樣就能夠根據世界杯舉辦的年份添加一些div元素
按鈕包含20個元素,與每一屆的世界杯相對應

在 div 標簽中添加按鈕,及為世界杯的每一年添加適當的文本標簽。

  • 提示 1:要在 div 標簽中創建按鈕,你需要在頁面(目前還不存在)上選擇 div 元素。你將在帶有 years_buttons 類的父 div 元素中創建這些 div 元素。

  • 提示 2:你前期創建的 years 變量包含世界杯的年份。years 數組顯示為 [1930, 1934, ...]。

  • 提示 3:帶有“year_buttons”類的 div 元素將包含所有按鈕的 div。現在,假設你需要在 div 元素中添加 div 并將數據綁定到新的 div。使用以下常見的 D3 模式將數據綁定到頁面:
    .selectAll()
    .data()
    .enter()
    .append()

  • 提示 4:你需要使用 .text() 函數和匿名讀取函數,向每個按鈕添加年份作為文本。匿名讀取函數比你之前見過的要簡單。思考你需要從年數組中訪問到什么。由于 .text() 的原因,你無需將年份的數據類型從 Integer 改為 String。
    Jonathan 引用來樣式化按鈕的 CSS 代碼如下所示。你可以將此代碼添加到 HTML 文件頂部的樣式標簽之間。

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. 延遲顯示按鈕

要確保放完按鈕的動態圖片之前,按鈕不會出現在頁面上

44. 向按鈕添加事件

語法是on函數,第一個參數是你想要觸發回調函數的事件,第二個參數是你想運行的函數
元素中出現指定事件時,有時可能會采用事件處理程序,傳遞給事件處理程序的參數d與傳遞至d3中大部分訪問函數的參數d是一致的

d3訪問點擊事件的運作方式
this大部分時候代表的是被點擊的元素本身

Javascript 的 'this'

清楚理解并掌握 JavasScript 的 'this'

如果你需要可以快速查閱的信息,此篇博文的部分內容向你提供了簡潔明了的解釋。

如需深入研究 JavaScript 的關鍵詞 this,你可以就關鍵詞 this 學習面向對象的 JavaScript 課程

Javascript 事件

D3.js 鼠標事件(作者:Anthony Nosek)

鼠標懸停、鼠標移出、鼠標按下教程(作者:Christophe Viau)

向 D3.js 圖形添加工具提示

48.Matt 關于制作地圖的提示

地圖是一種特殊且難以表現的數據,不過地圖的表現力也很強
制作地圖前很重要的一點是你想讓觀眾了解什么,以及你要如何來展示
注意刻度和變量的使用,確保觀眾能看出明確的結果
不論做什么圖表,提前思考都很重要,想清楚你想讓人們了解什么
作為一名數據科學家,你的工作是告訴人們他們需要了解什么,因此你可以采用取標題、使用軸標簽的方法來表達重要的內容,還可以使用注釋來標明特定事件或者異常數值

示例

https://plot.ly/~etpinard/453/average-daily-surface-air-temperature-anomalies-c-in-july-2014-with-respect-to-1/

溫度異常值是長期平均溫度的差值。(來源

https://plot.ly/~MattSundquist/878/the-1000-most-populous-canadian-cities/

D3 資源

讓我們制作地圖(作者:Mike Bostock)

讓我們制作氣泡圖(作者:Mike Bostock)

使用 D3 制作出的多個小型地圖

已被解釋過的 D3.js 簡單地圖

如何在 D3 中制作面量圖(作者:EJ Fox)

其他資源

R 地圖包文檔

Python 中的工作草圖

視頻 2:13 處,Matt 提到用來制作地圖的 GUI。圖形用戶界面 (Graphical User Interface) 或 GUI 是一款點擊式軟件,是命令行界面的替代方案。Tableau 和 Data Wrapper 是 Matt 提到的兩款 GUI。
Tableau
Data Wrapper

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1.發現故事 本課講述可視化用到的:敘事結構數據收集過程數據處理 2.新聞方法 給可視化添加語境圍繞數據進行敘事 ...
    esskeetit閱讀 2,946評論 0 2
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,881評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,099評論 25 708
  • 最近兩三年都沒整過什么創(幺)新(娥)了(子)!湊活看看這幾年前的拙劣手法。 首先在這里要給大家說!不能隨便摘花折...
    張可以覺得可以閱讀 759評論 12 9