Search code examples
matplotlibplotcolorslegendcartopy

create legend for markercolor and size


I've created the following figure:

enter image description here

With following code:

matplotlib.rcParams.update({'font.size': 10})

fig = plt.figure(figsize=(16, 9), dpi=300, facecolor='white')
ax = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(extent)

# cartopy layers
country_10m = cartopy.feature.NaturalEarthFeature('cultural', 'admin_0_countries', '10m')
ax.add_feature(country_10m, edgecolor='w', linewidth=0.75, facecolor='#EEEFEE', label='country border')
ax.coastlines(resolution='10m', color='#EEEFEE', linewidth=0.75)
ax.imshow(np.tile(np.array([[[191, 210, 217]]], dtype=np.uint8), [2, 2, 1]), origin='lower', transform=cartopy.crs.PlateCarree(), extent=extent)

ax.scatter(gdf_ldb.x, gdf_ldb.y, c= gdf_ldb.Color, s= gdf_ldb.Markersize, zorder=30) 
# ax.scatter(gdf_ports_filt.longitude, gdf_ports_filt.latitude, s= 10, color= 'k', zorder= 30)

ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=1, color='gray', alpha=0.5, linestyle='-')
ax.text(-0.08, 0.5, 'latitude [°]', va='bottom', ha='center',rotation='vertical', rotation_mode='anchor',transform=ax.transAxes);
ax.text(0.5, -0.09, 'longitude [°]', va='bottom', ha='center', rotation='horizontal', rotation_mode='anchor', transform=ax.transAxes);

How do I create a legend for the markersize as well for the color, so like this:

enter image description here

With x, x1, and x2 representing the values of the markersizes.

gdf_ldb looks like:

    x   y   Type    Color   Markersize  geometry
prograding_feature_polygon_29   12.857701   56.648035   Updrift grey    3.0 POINT (12.85770 56.64804)
prograding_feature_polygon_57   17.781445   54.808079   Updrift grey    3.0 POINT (17.78144 54.80808)
prograding_feature_polygon_58   17.438390   54.754518   Updrift grey    3.0 POINT (17.43839 54.75452)
prograding_feature_polygon_63   4.708077    52.880322   Updrift grey    3.0 POINT (4.70808 52.88032)
prograding_feature_polygon_72   3.953364    51.842299   Updrift grey    3.0 POINT (3.95336 51.84230)
... ... ... ... ... ... ...
retreating_feature_polygon_2018 -10.148432  53.415224   Double Updrift  grey    3.0 POINT (-10.14843 53.41522)
retreating_feature_polygon_2019 -9.954510   54.197329   Double Updrift  grey    3.0 POINT (-9.95451 54.19733)
retreating_feature_polygon_2119 15.095564   37.389535   Double Updrift  grey    3.0 POINT (15.09556 37.38953)
retreating_feature_polygon_2120 14.317893   37.025026   Double Updrift  grey    3.0 POINT (14.31789 37.02503)
retreating_feature_polygon_2121 13.952111   37.101009   Updrift grey    3.0 POINT (13.95211 37.10101)



Thanks in advance,

Dante

Solution

  • The answer by Rutger Kassies is excellent for many use cases. However, he mentions that One can also combine the handles and labels of both and plot them in a single legend if needed.

    Here I offer another answer that shows the steps to create the single legend manually. Inside the single legend, 2 groups of sub legends are created and arranged as needed.

    With single legend, you don't need to find the values of bbox_to_anchor for the second (or third and so on) to position them properly.

    With manual creation of items into a single legend, you have full control of the items' you need in the legend. However, it need some extra coding to achieve the goal.

    import matplotlib.pyplot as plt
    import matplotlib.lines as mlines
    import cartopy.crs as ccrs
    import cartopy.feature as cfeature
    
    # For `Categories` symbol
    # Each item of legends requires 3 properties: color/text/marker_shape
    color_V = ["green", "orange", "purple", "red", "cyan", "magenta"]
    text_V = ["cat_4", "cat_9", "cat_13", "cat_15", "cat_19", "cat_33"]
    marker_V = ["o", "o", "o", "o", "o", "o"]
    len_V = len(color_V)
    
    # For `Size/values` symbol
    color_S = ["gray", "gray", "gray", "gray"]
    sizes_S = [4, 8, 12, 16]         #increasing values ...
    text_S = ["4", "8", "12", "16"]  #cover `sizes1` below
    marker_S = ["o", "o", "o", "o"]  #use disk shape
    len_S = len(color_S)
    
    # Demo data locations and attributes
    xs = [23,12,4,25,24,52,17,33]
    ys = [41,12,32,15,35,21,23,43]
    colors1 = ["green", "orange", "purple", "red", "cyan", "magenta", "green", "orange"]
    #texts1 = ["4", "9", "13", "15", "19", "33", "4", "9"]
    markers1 = ["o", "o", "o", "o", "o", "o", "o", "o"]
    sizes1 = [10,16,9,12,7,4,2,6]
    len1 = len(xs)
    
    all_patches = [] #for items in a single legend
    
    # Create figure and `ax` for map plotting
    # This form can create a single axes or an array of axes
    fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8,6), subplot_kw={'projection': ccrs.PlateCarree()})
    
    # All steps of plots will be done on `ax`
    
    # [1] Add an invisible object as a spacer in the legend box
    #rect = mpatches.Rectangle([0, 0], 0.01, 0.01, ec="none", color="lightgray")
    all_patches.append(mlines.Line2D([0, 0], [1, 0], color="none"))
    
    # Explicitly defining the elements in the legend
    # [2] Add proxied text: 'Categories' to the legend
    line = mlines.Line2D([0, 0], [1, 0], lw=.5, alpha=0.9, color="none")
    line.set_label('Categories')  # Title for 1st group of symbols in the legend
    all_patches.append(line)
    
    # [3] Plot (on the axes) `none` data point and 
    #  save the output patches for `Categories` group
    patches_V = [ ax.plot([],[], marker=marker_V[i], ms=8, ls="", color=color_V[i], \
              label="{:s}".format(text_V[i]) )[0] \
              for i in range(len_V) ]
    all_patches += patches_V
    
    # [4] Add an invisible object as a spacer in the legend box
    all_patches.append(mlines.Line2D([0, 0], [1, 0], color="none"))
    
    # [5] Add proxied text: 'Sizes' to the legend
    x, y = ([0, 1], [0, 0])
    line = mlines.Line2D([0, 0], [1, 0], lw=.5, alpha=0.9, color="none")
    line.set_label('Sizes')  # Title for 2nd group of symbols in the legend
    all_patches.append(line)
    
    
    # [6] Create patches for `Sizes` group
    patches_S = [ ax.plot([],[], marker=marker_S[i], ms=sizes_S[i], ls="", \
            color=color_S[i], \
            label="{:s}".format(text_S[i]) )[0] for i in range(len_S) ]
    
    all_patches += patches_S
    
    # Plot point data using the demo data
    for i in range(len1):
        ax.plot(xs[i], ys[i], marker=markers1[i], ms=sizes1[i], color=colors1[i])
    
    ax.set_extent([0, 80, 0, 60])
    
    # Plot the legend in the upper-right corner
    combined_legend = ax.legend(handles=all_patches, 
                                bbox_to_anchor=(1, 1), 
                                title="The Legend", 
                                loc='upper right', 
                                ncol=1, 
                                numpoints=1, 
                                facecolor="lightgray", 
                                fontsize = 10, 
                                title_fontsize= 12, 
                                labelspacing = 0.55, 
                                shadow=True)
    
    # Draw some basemap features
    ax.coastlines(lw=0.3, color="k")
    ax.add_feature(cfeature.LAND)
    ax.add_feature(cfeature.OCEAN)
    plt.title("Legend for Categories and Sizes")
    plt.show()
    

    The output map:

    single_legend