❝本文完整代码及数据已上传至
Github
仓库https://github.com/CNFeffery/FefferyViz ❞
1 简介
在星球研究所最近的《10万座大坝的诞生!》一文中,作者们利用丰富的数据可视化手段对我国及世界大型水坝工程的发展分布情况进行了分析展示,而我尤其喜爱其中的一幅作品:
图1
这是一幅信息量丰富且难度较大的数据可视化作品,因为它混合了地理信息可视化与象形柱状图,使得绘制它需要多方面的数据可视化知识。
复刻有挑战性的数据可视化作品正是我这个系列文章的主旨,在今天的文章中,我就将基于Python
,教大家如何还原出这幅作品中的主要视觉元素。
2 复刻过程
首先,按照我们这个系列文章的传统,先来对原作品中的视觉元素进行剖析,进而构思出“逐一攻破”的方法:
2.1 拆解主要视觉元素
- 「半球部分」
这幅作品首先映入眼帘的自然是其上方对称布局的两个半球图像,要绘制它们其实比较简单,我们可以利用地球「正射投影」(Orthographic projection),分别选定不同的中央经纬度,便可得到左右不同视角下的半球。
图2
- 「象形柱状图部分」
原作品中下方部分的象形柱状图也是非常的形象生动切合主题,通过观察可以发现每个大坝logo代表数值200,而末尾不足200的部分就按照余数/200
的「透明度」进行渲染,配合右下角的图例,帮助读者快速解读出信息,这实现起来也不难,我将会使用matplotlib
的相关API配合循环语句来实现logo图片的嵌入。
- 「其余辅助视觉元素」
除了上面介绍的两部分视觉主体之外,其余的部分都主要是些文字或符号之类的小部件,模仿起来比较简单(上方地图的国家名称标注部分用代码自动化的方式反而更费事,因此本文模仿过程略去这部分),可以明显看出的是其主要的标题等文字内容主要使用了「思源宋体」。
摸清楚要做的内容之后,下面让我们开始吧!
2.2 半球部分的制作
「正射投影」部分,我选择使用cartopy.ccrs
内置的Orthographic()
,通过传入中央经纬度,即可得到期望的半球面:
图3
我们利用mpl_toolkits.axes_grid1.inset_locator
中的inset_axes()
将两个半球各自对应的axe
对象插入到主体axe
中,再利用cartopy
的add_geometries
进行矢量元素的叠加和色彩映射即可,我已经在数据中算好了归一化数值方便色彩映射,以左半球为例:
fig, ax = plt.subplots(figsize=(5.4, 8.1))
# 构建左半球图像
map_left = inset_axes(ax, width='100%', height='100%',
bbox_to_anchor=(0.19, 0.58, 0.26, 0.26),
bbox_transform=ax.transAxes,
axes_class=cartopy.mpl.geoaxes.GeoAxes,
axes_kwargs=dict(map_projection=crs_left))
# 添加陆地面并设置颜色
map_left.add_feature(cfeature.NaturalEarthFeature('physical', 'land', '110m',
edgecolor='none',
facecolor='#d9d9d9'))
# 为不同国家添加面要素并按照归一化数值上色
for country in ["美国", "墨西哥", "巴西"]:
map_left.add_geometries(data.query('国家 == @country').geometry,
crs=ccrs.PlateCarree(),
facecolor=custom_cmap(data.query('国家==@country').iat[0, -2]),
zorder=999)
# 设置画框样式
map_left.spines['geo'].set_linewidth(0.6)
map_left.spines['geo'].set_color('#d9d9d9')
图4
这样我们就完成了两个半球部分的制作,顺便配合matplotlib
中的text()
、参数fontproperties
以及matplotlib.font_manager
来基于思源宋体
添加标题:
图5
2.3 象形柱状图部分的制作
介绍完「半球」地图部分,我们接着来制作「象形柱状图」部分,这部分的核心内容是使用matplotlib.offsetbox
下的OffsetImage()
、AnnotationBbox()
,配合matplotlib
自带的add_artist()
,向现有的图床中插入外部图片(这里的logo图片是我通过软件手绘的~)。
其中OffsetImage()
传入图片数组变量、缩放比例以及透明度;AnnotationBbox()
用于调整所插入图片在图中的位置,遵守一行10个logo的最大布局数量,略微构思一下嵌套循环过程,微调位置参数,即可在前面的基础上,得到下面的图像:
图6
而右下角图例中的第一个logo上下渐变的效果其实是配合numpy
数组,从上往下线性地降低rgba第四个通道的透明度值得到了,非常的容易~
图7
而图中其他的小元素譬如各种文字就不赘述了,无非是text()
复制粘贴改改参数而已,对完整过程感兴趣的朋友可以在文章开头的Github
仓库中找到对应数据和代码~