如何在Matplotlib中为两点绘制弧线以完成对比图表?
问题:用Matplotlib重现带弧线连接的国家数据图表
我需要重现一张包含弧线连接同国家不同年份数据点的图表,目前已完成基础部分,但无法在对应年份的点之间创建弧线。
目标效果说明
目标图表中,每个国家对应两个数据点(2004年和2022年),同国家的两点之间用弧线连接;点上标注年份后两位,同时有一条对角线作为参考线。
现有代码
import matplotlib.pyplot as plt from scipy.interpolate import interp1d import numpy as np import pandas as pd colors = ["#CC5A43","#2C324F","#5375D4",] data = { "year": [2004, 2022, 2004, 2022, 2004, 2022], "countries" : [ "Denmark", "Denmark", "Norway", "Norway","Sweden", "Sweden",], "sites": [4,10,5,8,13,15] } df= pd.DataFrame(data) df = df.sort_values([ 'year'], ascending=True ).reset_index(drop=True) df['ctry_code'] = df.countries.astype(str).str[:2].astype(str).str.upper() df['year_lbl'] ="'"+df['year'].astype(str).str[-2:].astype(str) sites = df.sites lbl1 = df.year_lbl fig, ax = plt.subplots( figsize=(6,6),sharex=True, sharey=True, facecolor = "#FFFFFF", zorder= 1) ax.scatter(sites, sites, s= 340, c= colors*2 , zorder = 1) ax.set_xlim(0, sites.max()+3) ax.set_ylim(0, sites.max()+3) ax.axline([ax.get_xlim()[0], ax.get_ylim()[0]], [ax.get_xlim()[1], ax.get_ylim()[1]], zorder = 0, color ="#DBDEE0" ) for i, l1 in zip(range(0,6), lbl1) : ax.annotate(l1, (sites[i], sites[i]), color = "w",va= "center", ha = "center") ax.set_axis_off()
当前问题
目前生成的图表只有数据点和对角线,无法实现同国家两点间的弧线连接,尝试过mpatches.Arc以及基于patches和path的方法均未成功。
解决方案
推荐使用贝塞尔曲线绘制弧线,方式灵活且易调整,以下是修改后的完整代码:
完整实现代码
import matplotlib.pyplot as plt import numpy as np import pandas as pd from matplotlib.path import Path import matplotlib.patches as patches colors = ["#CC5A43","#2C324F","#5375D4",] data = { "year": [2004, 2022, 2004, 2022, 2004, 2022], "countries" : [ "Denmark", "Denmark", "Norway", "Norway","Sweden", "Sweden",], "sites": [4,10,5,8,13,15] } df= pd.DataFrame(data) # 按国家分组,方便获取同国家的两个数据点 df_grouped = df.groupby("countries") fig, ax = plt.subplots(figsize=(6,6), facecolor="#FFFFFF") # 绘制对角线参考线 ax.axline([0, 0], [df['sites'].max()+3, df['sites'].max()+3], zorder=0, color="#DBDEE0") # 遍历每个国家,绘制点和连接弧线 for idx, (country, group) in enumerate(df_grouped): # 获取两个年份的坐标点 x1, y1 = group[group['year']==2004]['sites'].values[0], group[group['year']==2004]['sites'].values[0] x2, y2 = group[group['year']==2022]['sites'].values[0], group[group['year']==2022]['sites'].values[0] # 绘制两个数据点 ax.scatter([x1, x2], [y1, y2], s=340, c=colors[idx], zorder=2) # 添加年份标注 ax.annotate(f"'{str(2004)[-2:]}", (x1, y1), color="w", va="center", ha="center") ax.annotate(f"'{str(2022)[-2:]}", (x2, y2), color="w", va="center", ha="center") # 计算贝塞尔曲线控制点,控制弧线弯曲程度 mid_x = (x1 + x2) / 2 mid_y = (y1 + y2) / 2 + 2 # 数值越大,弧线向上凸的程度越高 # 定义贝塞尔曲线路径 verts = [ (x1, y1), # 起点 (mid_x, mid_y), # 控制点 (x2, y2), # 终点 ] codes = [ Path.MOVETO, Path.CURVE3, Path.LINETO, ] path = Path(verts, codes) patch = patches.PathPatch(path, facecolor='none', edgecolor=colors[idx], linewidth=2, zorder=1) ax.add_patch(patch) # 设置坐标轴范围 ax.set_xlim(0, df['sites'].max()+3) ax.set_ylim(0, df['sites'].max()+3) ax.set_axis_off() plt.show()
关键说明
- 分组处理数据:通过
groupby("countries")获取每个国家的两个数据点,避免手动索引的繁琐操作。 - 贝塞尔曲线绘制弧线:使用
PathPatch和贝塞尔曲线定义弧线,调整控制点的y值(mid_y = (y1 + y2)/2 + 2)可控制弧线弯曲程度。 - 层级控制:将弧线的
zorder设为1,低于数据点的zorder=2,确保数据点显示在弧线上方。
内容的提问来源于stack exchange,提问作者ruthpozuelo
相关产品推荐
相关产品推荐

