在前面的博客中已经介绍了如何绘制地图,这一节学习如何绘制地区分布图。如果对绘制地图还不熟悉的话可以了解一下之前我写的博客:数据可视化【十】绘制地图
Intergrating(整合) TopoJSON with tabular data(列表数据)
在前面的博客中没有使用到tsv
文件,因为我找到的TopoJSON中本来就包含国家的名字。但是如果想要知道国家更多的数据仅仅知道国家的名字是不够的,因此还需要导入关于国家其他数据的tsv
文件。当导入多个文件的时候最好使用Promis.all
,这样就可以在文件全部都加载后再开始执行。需要将多个任务都放在一个数组里面,然后再在then
中用一个数组接收读入的数据。
Promise.all([
tsv('https://unpkg.com/world-atlas@1.1.4/world/50m.tsv'),
json('https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-50m.json')
]).then(([tsvData, topsJSONdata]) => {
}
为了快速处理tsv
文件,我们使用reduce
函数将其放到一个类中。其中iso_n3
与JSON
文件中的id
对应
const rowById = tsvData.reduce((accumulator, d)=>{
accumulator[d.iso_n3] = d;
return accumulator;
}, {});//前面是reduce的执行,后面是accumulator的初始值
为了使得JSON
文件中的每个元素都包含这个国家更多的信息,我们使用assign
函数将同一个国家的信息进行合并
const countries = feature(topsJSONdata, topsJSONdata.objects.countries);
// console.log(countries);
countries.features.forEach(d => {
Object.assign(d.properties, rowById[d.id]);
});
Creating a loadAndProcessData module
上面载入数据的过程其实可以当作另一个模块,因此我们把上面的代码单独放在一个用于加载数据的文件中
loadAndProcessData.js
import {json, tsv} from 'd3';
import {feature} from 'topojson';
export const loadAndProcessData = () => Promise.all([
tsv('https://unpkg.com/world-atlas@1.1.4/world/50m.tsv'),
json('https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-50m.json')
]).then(([tsvData, topsJSONdata]) => {
const rowById = tsvData.reduce((accumulator, d)=>{
accumulator[d.iso_n3] = d;
return accumulator;
}, {});
const countries = feature(topsJSONdata, topsJSONdata.objects.countries);
// console.log(countries);
countries.features.forEach(d => {
Object.assign(d.properties, rowById[d.id]);
});
return countries;
});
并在index.js中将相应的代码都放在loadAndProcessData
函数的then
中
Visualizing an ordered attribute with color
在得到国家的其他属性之后,我们就可以根据国家的其他标签给国家上色
我们可以首先观察一下tsv
文件中都有什么。太多了这里就不显示了,比如说里面有一个标签是经济,我们不妨使用经济给不同国家不同的颜色。
为了方便修改标签,我们把获取数据属性的操作放到一个函数里,并且在后面所有的地方都通过这个函数获取属性,这样做的好处是如果我们不想要这个标签,我们只需要在这一个地方进行修改就可以。
我们使用scaleOrdinal
来得到从标签到颜色的映射,对于标签我们需要进行一定的排序处理,而颜色区间d3
有现成的函数可以让我们很方便的得到渐变的颜色从而区分不同的等级。
我们可以搜索d3-scale-chromatic
,里面有很多函数。在这里我们选择schemeSpectral[k]
,这个k
是标签分类的个数,我们可以直接得到:
const colorValue = d => d.properties.income_grp
const colorScale = scaleOrdinal()
.domain(countries.features.map(colorValue));
colorScale
.domain(colorScale.domain().sort().reverse())
.range(schemeSpectral[colorScale.domain().length]);
制作好颜色标签以后就是在添加path
的后面设置fill
属性,我之前做的是随机选择一个颜色,这里就改为根据上面已经设置好的颜色尺得到对应的颜色。
Showing data from 2 attributes in tooltip
在得到好看的颜色以后,我们还希望对应的标签的内容可以改变,这一点很容易再到,只需要在设置text
的时候加上对应的属性即可。
g.selectAll('path').data(countries.features)
.enter().append('path')
.attr('class', 'country')
.attr('fill', d => colorScale(colorValue(d)))
.attr('d', pathGenerator)
.append('title')//添加title,然后鼠标放在上面就可以出现标题
.text(d => d.properties.name + ':' + colorValue(d));
初步效果图如下:
Adding a color legend
我们还希望添加一个颜色图例,这样就可以很方便地知道什么颜色对应的是什么内容而不必要一一去看。根据上一节(数据可视化【十二】颜色图例和尺寸图例)我们制作的颜色图例,可以直接进行使用。
index.js
colorLegendG.call(colorLegend, {
colorScale,
circleRadius: 8,
spacing : 20,
textOffset : 15,
backgroundRectWidth: 250
});
在colorLegend.js中,图例有一个背景,通过添加一个矩形来实现
const backgroundRect = selection.selectAll('rect')
.data([null]);
const n = colorScale.domain().length;
backgroundRect.enter().append('rect')
.merge(backgroundRect)
.attr('x', -circleRadius * 2)
.attr('y', -circleRadius * 2)
.attr('rx', circleRadius * 2)
.attr('width', backgroundRectWidth)
.attr('height', spacing * n + circleRadius * 3)
.attr('fill', 'white')
.attr('opacity', 0.8);
最后效果图:
代码地址:https://vizhub.com/Edward-Elric233/635845fd4c8b4917b999b18cab5e9b09