动画与互动

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,841评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,415评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,904评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,051评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,055评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,255评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,729评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,377评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,517评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,420评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,467评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,144评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,735评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,812评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,029评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,528评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,126评论 2 341

推荐阅读更多精彩内容

  • 1.发现故事 本课讲述可视化用到的:叙事结构数据收集过程数据处理 2.新闻方法 给可视化添加语境围绕数据进行叙事 ...
    esskeetit阅读 2,774评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,295评论 25 707
  • 最近两三年都没整过什么创(幺)新(娥)了(子)!凑活看看这几年前的拙劣手法。 首先在这里要给大家说!不能随便摘花折...
    张可以觉得可以阅读 730评论 12 9