matplotlib 如何为GeoAxes子图创建单个地物图例

pinkon5k  于 2023-11-22  发布在  其他
关注(0)|答案(2)|浏览(273)

我已经看了这里的许多其他问题,试图解决这个问题,但无论出于什么原因,我不能。每个解决方案似乎给我同样的错误,给予,或返回什么都没有。
我有一个列表,其中有六个我正在循环创建一个6Map的图形。每个Map的格式类似,唯一的区别是它们的时间列。每个Map都有通过制图创建的相同的分类方案。颜色是由颜色表确定的,Map本身没有与值相关的颜色。我希望所有Map都有一个单一的图例,以便读者更容易看到,下面是我的代码:

  1. import cartopy.crs as ccrs
  2. import cartopy.feature as cfeature
  3. import mapclassify
  4. from matplotlib.colors import rgb2hex
  5. from matplotlib.colors import ListedColormap
  6. plt.style.use('seaborn-v0_8-dark')
  7. # Define the Robinson projection
  8. robinson = ccrs.Robinson()
  9. # Create a 3x2 grid of subplots
  10. fig, axs = plt.subplots(3, 2, figsize=(12, 12), subplot_kw={'projection': robinson})
  11. # Flatten the subplot array for easy iteration
  12. axs = axs.flatten()
  13. # Define color map and how many bins needed
  14. cmap = plt.cm.get_cmap('YlOrRd', 5) #Blues #Greens #PuRd #YlOrRd
  15. # Any countries with NaN values will be colored grey
  16. missing_kwds = dict(color='grey', label='No Data')
  17. # Loop through the dataframes and create submaps
  18. for i, df in enumerate(dataframes):
  19. # Create figure and axis with Robinson projection
  20. mentionsgdf_robinson = df.to_crs(robinson.proj4_init)
  21. # Plot the submap
  22. ax = axs[i]
  23. # Add land mask and gridlines
  24. ax.add_feature(cfeature.LAND.with_scale('50m'), facecolor='lightgrey')
  25. gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
  26. linewidth=1, color='gray', alpha=0.3, linestyle='--')
  27. gl.xlabel_style = {'fontsize': 7}
  28. gl.ylabel_style = {'fontsize': 7}
  29. # Classification scheme options: EqualInterval, Quantiles, NaturalBreaks, UserDefined etc.
  30. mentionsgdf_robinson.plot(column='mentions',
  31. ax=ax,
  32. legend=True, #True
  33. cmap=cmap,
  34. legend_kwds=({"loc":'center left', 'title': 'Number of Mentions', 'prop': {'size': 7, 'family': 'serif'}}),
  35. missing_kwds=missing_kwds,
  36. scheme="UserDefined",
  37. classification_kwds = {'bins':[20, 50, 150, 300, 510]})
  38. # Set the titles of each submap
  39. ax.set_title(f'20{i+15}', size = 15, family = 'Serif')
  40. # Define the bounds of the classification scheme
  41. upper_bounds = mapclassify.UserDefined(mentionsgdf_robinson.mentions, bins=[20, 50, 150, 300, 510]).bins
  42. bounds = []
  43. for index, upper_bound in enumerate(upper_bounds):
  44. if index == 0:
  45. lower_bound = mentionsgdf_robinson.mentions.min()
  46. else:
  47. lower_bound = upper_bounds[index-1]
  48. bound = f'{lower_bound:.0f} - {upper_bound:.0f}'
  49. bounds.append(bound)
  50. # replace the legend title and increase font size
  51. legend_title = ax.get_legend().get_title()
  52. legend_title.set_fontsize(8)
  53. legend_title.set_family('serif')
  54. # get all the legend labels and increase font size
  55. legend_labels = ax.get_legend().get_texts()
  56. # replace the legend labels
  57. for bound, legend_label in zip(bounds, legend_labels):
  58. legend_label.set_text(bound)
  59. fig.suptitle(' Yearly Country Mentions in Online News about Species Threatened by Trade ', fontsize=15, family = 'Serif')
  60. # Adjust spacing between subplots
  61. plt.tight_layout(pad=4.0)
  62. # Save the figure
  63. #plt.savefig('figures/submaps_5years.png', dpi=300)
  64. # Show the submap
  65. plt.show()

字符串
这是我现在的结果,我希望在Map的中心位置有一个图例。
x1c 0d1x的数据
我尝试了here建议的代码,但只收到一个UserWarning:Legend does not support handles for PatchCollection instances.此外,我不知道如何在循环之外合并所有其他需要的Legend修改(边界,字体,bin等)。

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


以下是2015-2017年三年的数据:https://jmp.sh/s/ohkSJpaMZ4c1GifIX0nu
下面是我使用的全局shapefile的所有文件:https://jmp.sh/uTP9DZsC
使用这些数据和下面的代码应该可以让你运行上面分享的完整的可视化代码。谢谢。

  1. import geopandas as gpd
  2. import pandas as pd
  3. # Read in globe shapefile dataframe
  4. world = gpd.read_file("TM_WORLD_BORDERS-0.3.shp")
  5. # Read in sample dataframe
  6. df = pd.read_csv("fifsixseventeen.csv", sep = ";")
  7. # Separate according to date column
  8. fifteen = df[(df['date'] == 2015)].reset_index(drop=True)
  9. sixteen = df[(df['date'] == 2016)].reset_index(drop=True)
  10. seventeen = df[(df['date'] == 2017)].reset_index(drop=True)
  11. # Function to merge isocodes of the countries with world shapefile
  12. def merge_isocodes(df):
  13. # Groupby iso3 column in order to merge with shapefile
  14. allmentions = df.groupby("iso3")['mentions'].sum().sort_values(ascending = False).reset_index()
  15. # Merge on iso3 code
  16. mentionsgdf = pd.merge(allmentions, world, left_on=allmentions["iso3"], right_on=world["ISO3"], how="right").drop(columns = "key_0")
  17. # Redefine as a geodataframe
  18. mentionsgdf = gpd.GeoDataFrame(mentionsgdf, geometry='geometry')
  19. return mentionsgdf
  20. onefive = merge_isocodes(fifteen)
  21. onesix = merge_isocodes(sixteen)
  22. oneseven = merge_isocodes(seventeen)
  23. # Create a list to store each years' dataframes
  24. dataframes = [onefive, onesix, oneseven]

6ss1mwsb

6ss1mwsb1#

  • 这些Axescartopy.mpl.geoaxes.GeoAxes
  • axs[0].get_legend_handles_labels()产生UserWarning: Legend does not support handles for PatchCollection instances.,并返回([], [])
  • 使用Axes.get_legend()获取Legend示例。
  • .legend_handlesmatplotlib 3.7.2中是新的,它返回Artist对象的列表。或者,使用.legendHandles,但它已被弃用。
  • 有关图例定位的详细信息,请参见How to put the legend outside the plot
  • Moving matplotlib legend outside of the axis makes it cutoff by the figure boxfig.savefig('fig.png', bbox_inches='tight')
  • 注意:假设所有图例都相同。
  • python 3.9.18geopandas 0.12.2matplotlib 3.7.2cartopy 0.22.0中测试。
  1. ...
  2. for i, df in enumerate(dataframes):
  3. ...
  4. # after the for loop, use the following code
  5. # extract the legend from an axes - used the last axes for the smaller sample data
  6. l = axs[2].get_legend()
  7. # extract the handles
  8. handles = l.legend_handles
  9. # get the label text
  10. labels = [v.get_text() for v in l.texts]
  11. # get the title text
  12. title = l.get_title().get_text()
  13. # create the figure legend
  14. fig.legend(title=title, handles=handles, labels=labels, bbox_to_anchor=(1, 0.5), loc='center left', frameon=False)
  15. # iterate through each Axes
  16. for ax in axs:
  17. # if the legend isn't None (if this condition isn't required, remove it and use only ax.get_legend().remove())
  18. if gt := ax.get_legend():
  19. # remove the legend
  20. gt.remove()

字符串


的数据

展开查看全部
bq9c1y66

bq9c1y662#

这可能不是你正在寻找的解决方法,但我在过去已经成功地实现了。本质上,你只放置一个子图的图例,并手动操纵其位置和大小,使其落在整个图的适当位置的单个子图之外。
使用边界框函数:bbox_to_anchor()将图例放置在您选择的子图之外。
然后,使用transform参数从数据切换到轴坐标。具体来说,如果相对于axs[k]的坐标放置,那么代码可能如下所示:
axs[k].legend(bbox_to_anchor = (m,n), transform=axs[k].transAxes, borderaxespad=0)
其中m和n是控制图例位置的浮点数。这些(以我的经验)通常是通过试错来确定的。
进一步的阅读和实现mpl文档可以在here中找到。

相关问题