You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 16:55:31