(在模仿中精进数据可视化08)哪个省份的学子是熬夜冠军?
本文完整代码及数据已上传至我的
Github
仓库https://github.com/CNFeffery/FefferyViz
1 简介
大家好~热衷于钻研复刻优秀数据可视化作品的费老师我🧐,最近的业余时间主要沉迷于撰写Python+Dash快速web应用开发系列文章,在模仿中精进数据可视化系列文章有两个月没更新了,今天继续捡起来🥳。
我们今天要复刻的数据可视化作品,是前段时间在微博刷屏的下面这张网易数读
的作品,基于作业帮
的用户画像数据对哪个地方的学习是“熬夜冠军”进行了可视化表达:

而下面我们就来基于matplotlib
,复刻出这幅作品~
2 复刻过程
2.1 拆解主要视觉元素#
其实这幅作品有些类似于我们这个系列文章开篇那一期贝壳研究院的图,都是以半边扇形为主体构图元素,在极坐标中对数据进行一系列表达,而今天的案例我们构建扇形图表选择的是matplotlib
中的极坐标系,非常简单方便。
按照惯例,我们先来“肢解”一下这幅图的主要构图元素:
- 多子图组合
这幅作品中主要可以分为主体扇形子图和右下角略微“出墙来”的点缀扇形子图构成,我们可以使用plt.subplots()
创建底层画板之后,再分别用fig.add_axes(rect, polar=True)
来在不同位置插入不同大小的上述子图;
- 主体扇形底色交替填充
首先我们可以观察到在这幅图的主体扇形右半圆中,背景色是由颜色交替切换的子扇形区域构成的,且仔细观察可以发现子扇形之间的交界处是有白色边界线的。
这部分我们就可以使用到matplotlib
中的fill_between()
区域色彩填充功能,先生成指定数量的右半圆等弧度集合,其作用于极坐标系时传入的第一个参数为角度范围,第二个参数为填充起点半径值,第三个参数为填充终点半径值,白色交界线直接使用plot()
绘制直线即可;
- 极坐标柱状图与中央虚线
在上述构建的交替底色的基础上,我们继续来将每个地区的数值映射为极坐标柱状图的柱体高度,注意,这里的柱体颜色也是交替切换的,并且需要给每个柱体中央添加虚线点缀;
- 主体扇形多规则文字标注
在原作品中的地区及深夜学习活跃指数在角度旋转上有三种规则方式,我们可以在一开始构建数据时针对不同排名的地区,打上用于区别类型的标签,好在之后的绘图过程中分别控制角度旋转计算方式:


至于其他的点缀元素,就不详细说了,文章结尾的绘图代码里都有详细的注释。
2.2 完成复刻#
在上述拆解的基础上,我们就可以充分运用弧度跟角度之间的转换,配合matplotlib
和numpy
来复刻出下面的效果啦,最后裁剪出的作品如下,是不是相当还原呢~:

再放一张没有拆掉“脚手架”(坐标轴线)的效果,你就会更加清楚我的构图逻辑了:

完整代码如下,如有疑问欢迎在评论区与我进行交流:
# 生成每份子扇形区域的两边夹角
# 这里[::-1]是为了迎合matplotlib极坐标默认的角度位置
theta_group = (np.linspace(-0.5, 0.5, 32)*np.pi)[::-1]
# 创建图床和原始axes对象
fig, ax = plt.subplots(figsize=(10, 10))
############################
# 主体部分
############################
# 向原始图床中插入极坐标系新axes对象
ax1 = fig.add_axes([-0.5, 0, 1, 1], polar=True)
# 绘制右半边扇形区域最底层错落的色带填充
for idx, group in enumerate(fc.pairwise(theta_group)):
# 当下标为偶数时,填充#e3effd色
if idx % 2 == 0:
ax1.fill_between(group, 0.75, 3, facecolor='#e3effd')
# 当下标为奇数时,填充#fafbff色
else:
ax1.fill_between(group, 0.75, 3, facecolor='#fafbff')
# 绘制每份子扇形区域的中央虚线
for idx, group in enumerate(fc.pairwise(theta_group)):
theta = (group[0] + group[1]) / 2
ax1.plot([theta, theta], [0.75, 2.68], linestyle='--', color='#9fa0a0', linewidth=0.25)
# 绘制极坐标柱状图,分别占据每份子扇形区域的对应外扩长度
for idx, group in enumerate(fc.pairwise(theta_group)):
theta = (group[0] + group[1]) / 2
ax1.bar([theta], [2.25*data.at[idx, '深夜学习活跃指数']*0.01],
width=[np.pi / 32], bottom=0.75,
# 对下标分别为偶数与奇数的扇形绘制不同颜色
facecolor='#6785f2' if idx % 2 != 0 else '#7171fe',
edgecolor='white', linewidth=0.1, alpha=0.95, zorder=9)
# 绘制子扇形区域之间交界处的白色边界
for theta in theta_group:
ax1.plot([theta, theta], [1, 3], color='white', linewidth=0.2)
def rotate_text(text, group, method):
if method == 1:
return text, ((group[0] + group[1]) * 0.5 / np.pi) * 180 - 90
elif method == 2:
return '\n'.join(list(text)), ((group[0] + group[1]) * 0.5 / np.pi) * 180
elif method == 3:
return text, ((group[0] + group[1]) * 0.5 / np.pi) * 180 - 90 + 180
# 地区+数值文字标注
for idx, group in enumerate(fc.pairwise(theta_group[::-1])):
# 控制向data表的索引不越界
if idx < 31:
# 控制第一名的特殊字体颜色
if data.at[30-idx, '地区'] == '江苏':
text_color, value_color = 'white', 'white'
else:
text_color, value_color = 'black', '#595757'
# 利用前面定义的自编函数生成对应的文字与旋转角度
text, angle = rotate_text(data.at[30-idx, '地区'], group, method=data.at[30-idx, '文字排布'])
# 标注地区名称
ax1.annotate(text, xy=[(group[0]+group[1]) / 2, 2.925],
va='center', ha='center', zorder=10,
color=text_color,
rotation=angle,
fontsize=11)
# 标注深夜学习活跃指数
ax1.annotate(re.sub('\.$', '', str(data.at[30-idx, '深夜学习活跃指数'])[:4]),
xy=[(group[0]+group[1]) / 2, 2.79],
va='center', ha='center', zorder=10,
rotation=angle,
fontsize=10,
color=value_color,
fontproperties='Times New Roman')
# 绘制外围黑色虚线
ax1.plot(np.linspace(-0.38, 0.45, 1000)*np.pi, [3.275]*1000,
linestyle='dashed', color='#595655', linewidth=0.75)
# 添加“0~2点学习活跃指数”标注
ax1.annotate('\n'.join(list('0~2点学习活跃指数')),
xy=[0, 3.21],
va='center',
ha='center',
ma='center',
zorder=10,
rotation=0,
fontsize=11,
color='black',
fontproperties='Microsoft Yahei',
fontweight='bold',
bbox=dict(boxstyle="round", fc="white", ec="white", alpha=1))
############################
# 右下角点缀
############################
# 向原始图床中插入极坐标系新axes对象
ax2 = fig.add_axes([0.25, -0.7, 1, 1], polar=True)
theta_group2 = (np.linspace(-1.5, -0.5, 32)*np.pi)[::-1]
# 绘制左半边扇形区域最底层错落的色带填充
for idx, group in enumerate(fc.pairwise(theta_group2)):
# 当下标为偶数时,填充#e3effd色
if idx % 2 == 0:
ax2.fill_between(group, 0.75, 3, facecolor='#e3effd')
# 当下标为奇数时,填充#fafbff色
else:
ax2.fill_between(group, 0.75, 3, facecolor='#fafbff')
# 紧凑布局
fig.tight_layout(pad=0)
# 关闭所有axes的坐标轴线
ax.axis('off')
ax1.axis('off')
ax2.axis('off')
# 导出为图片
fig.savefig('图4.png', dpi=500, bbox_inches='tight', pad_inches=0, facecolor='white')
以上就是本文的全部内容,欢迎在评论区与我进行交流讨论~
作者:Feffery
出处:https://www.cnblogs.com/feffery/p/14599911.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2018-04-01 (数据科学学习手札22)主成分分析法在Python与R中的基本功能实现
2018-04-01 (数据科学学习手札21)sklearn.datasets常用功能详解