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

如何为GeoAxes子图创建单个统一图例

为Cartopy多子图添加统一图例

我通过循环处理6个仅时间列不同的DataFrame,生成3×2布局的Cartopy地图子图,所有地图采用相同的分类方案与颜色映射,但目前每个子图都带有独立图例,希望添加一个统一图例提升可读性。尝试过以下代码:

handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc='upper center')

但收到警告:Legend does not support handles for PatchCollection instances,同时不清楚如何将图例的分箱、字体等设置整合到循环外。


数据预处理代码

import geopandas as gpd
import pandas as pd

# 读取全球边界shapefile
world = gpd.read_file("TM_WORLD_BORDERS-0.3.shp")
# 读取样本数据
df = pd.read_csv("fifsixseventeen.csv", sep=";")

# 按年份拆分数据
fifteen = df[(df['date'] == 2015)].reset_index(drop=True)
sixteen = df[(df['date'] == 2016)].reset_index(drop=True)
seventeen = df[(df['date'] == 2017)].reset_index(drop=True)

# 合并ISO编码与地理数据的函数
def merge_isocodes(df):
    allmentions = df.groupby("iso3")['mentions'].sum().sort_values(ascending=False).reset_index()
    mentionsgdf = pd.merge(allmentions, world, left_on=allmentions["iso3"], right_on=world["ISO3"], how="right").drop(columns="key_0")
    mentionsgdf = gpd.GeoDataFrame(mentionsgdf, geometry='geometry')
    return mentionsgdf

onefive = merge_isocodes(fifteen)
onesix = merge_isocodes(sixteen)
oneseven = merge_isocodes(seventeen)

# 存储各年份数据框的列表
dataframes = [onefive, onesix, oneseven]

原始绘图代码

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import mapclassify
from matplotlib.colors import rgb2hex
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt

plt.style.use('seaborn-v0_8-dark')

# 定义Robinson投影
robinson = ccrs.Robinson()

# 创建3×2子图布局
fig, axs = plt.subplots(3, 2, figsize=(12, 12), subplot_kw={'projection': robinson})
axs = axs.flatten()

# 定义颜色映射与分箱数
cmap = plt.cm.get_cmap('YlOrRd', 5)
# 无数据区域设置为灰色
missing_kwds = dict(color='grey', label='No Data')

# 循环绘制子图
for i, df in enumerate(dataframes):
    mentionsgdf_robinson = df.to_crs(robinson.proj4_init)
    ax = axs[i]
    
    # 添加陆地与网格线
    ax.add_feature(cfeature.LAND.with_scale('50m'), facecolor='lightgrey')
    gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
                  linewidth=1, color='gray', alpha=0.3, linestyle='--')
    gl.xlabel_style = {'fontsize': 7}
    gl.ylabel_style = {'fontsize': 7}
    
    # 绘制分类地图(每个子图都生成图例)
    mentionsgdf_robinson.plot(column='mentions', 
                              ax=ax, 
                              legend=True,
                              cmap=cmap, 
                              legend_kwds={"loc":"center left", "title": "Number of Mentions", "prop": {"size": 7, "family": "serif"}},
                              missing_kwds=missing_kwds,
                              scheme="UserDefined", 
                              classification_kwds={'bins':[20, 50, 150, 300, 510]})
    
    # 设置子图标题
    ax.set_title(f'20{i+15}', size=15, family='Serif')
    
    # 修改子图图例标签与字体
    upper_bounds = mapclassify.UserDefined(mentionsgdf_robinson.mentions, bins=[20, 50, 150, 300, 510]).bins
    bounds = []
    for index, upper_bound in enumerate(upper_bounds):
        lower_bound = mentionsgdf_robinson.mentions.min() if index ==0 else upper_bounds[index-1]
        bounds.append(f'{lower_bound:.0f} - {upper_bound:.0f}')
    
    legend = ax.get_legend()
    legend.get_title().set_fontsize(8)
    legend.get_title().set_family('serif')
    for bound, label in zip(bounds, legend.get_texts()):
        label.set_text(bound)

fig.suptitle('Yearly Country Mentions in Online News about Species Threatened by Trade', fontsize=15, family='Serif')
plt.tight_layout(pad=4.0)
plt.show()

解决方案:手动构建统一图例

关键改动:

  1. 关闭子图自带图例:将绘图时的legend=True改为legend=False,避免每个子图生成重复图例
  2. 提前定义统一分箱与标签:因为所有子图用相同的分类规则,直接在循环外计算图例标签
  3. 手动创建图例色块:用matplotlib.patches.Patch生成对应每个分箱颜色的色块,包括无数据的灰色块
  4. 添加全局图例:用fig.legend()在指定位置添加统一图例

修改后的完整绘图代码

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import mapclassify
import matplotlib.pyplot as plt
from matplotlib.patches import Patch

plt.style.use('seaborn-v0_8-dark')

# 定义Robinson投影
robinson = ccrs.Robinson()

# 创建3×2子图布局
fig, axs = plt.subplots(3, 2, figsize=(12, 12), subplot_kw={'projection': robinson})
axs = axs.flatten()

# 定义颜色映射与分箱数
cmap = plt.cm.get_cmap('YlOrRd', 5)
# 无数据区域设置
missing_color = 'grey'
missing_label = 'No Data'

# 统一的分箱边界(所有子图共用)
bins = [20, 50, 150, 300, 510]
# 计算图例标签(取第一个数据框的最小值作为起始)
min_val = dataframes[0]['mentions'].min()
legend_labels = []
for idx, upper in enumerate(bins):
    if idx == 0:
        legend_labels.append(f'{min_val:.0f} - {upper:.0f}')
    else:
        legend_labels.append(f'{bins[idx-1]:.0f} - {upper:.0f}')
# 添加无数据标签
legend_labels.append(missing_label)

# 提取对应颜色:分箱颜色 + 无数据灰色
legend_colors = [cmap(i) for i in range(cmap.N)] + [missing_color]

# 循环绘制子图(关闭子图图例)
for i, df in enumerate(dataframes):
    mentionsgdf_robinson = df.to_crs(robinson.proj4_init)
    ax = axs[i]
    
    # 添加陆地与网格线
    ax.add_feature(cfeature.LAND.with_scale('50m'), facecolor='lightgrey')
    gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
                  linewidth=1, color='gray', alpha=0.3, linestyle='--')
    gl.xlabel_style = {'fontsize': 7}
    gl.ylabel_style = {'fontsize': 7}
    
    # 绘制分类地图:关闭子图图例
    mentionsgdf_robinson.plot(column='mentions', 
                              ax=ax, 
                              legend=False,  # 关键:关闭子图图例
                              cmap=cmap, 
                              missing_kwds={'color': missing_color},
                              scheme="UserDefined", 
                              classification_kwds={'bins': bins})
    
    # 设置子图标题
    ax.set_title(f'20{i+15}', size=15, family='Serif')

# 手动构建图例元素
legend_elements = [Patch(facecolor=color, edgecolor='black', label=label) 
                   for color, label in zip(legend_colors, legend_labels)]

# 添加全局统一图例
fig.legend(handles=legend_elements, 
           loc='upper right',  # 可调整位置:比如'center right'
           title='Number of Mentions',
           prop={'size': 8, 'family': 'serif'},
           bbox_to_anchor=(1.15, 0.9))  # 微调位置避免遮挡

fig.suptitle('Yearly Country Mentions in Online News about Species Threatened by Trade', fontsize=15, family='Serif')
plt.tight_layout(pad=4.0)
plt.show()

说明:

  • 所有子图共用同一套分箱规则,确保颜色映射完全一致
  • 手动构建的图例元素兼容fig.legend(),不会出现PatchCollection的报错
  • 可通过loc和bbox_to_anchor调整图例位置,比如放到子图区域外侧
  • 图例的字体、标题等设置可统一在fig.legend()的参数中调整

内容的提问来源于stack exchange,提问作者redleesy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 18:49:50