pythonmatplotlibplothistogrammatplotlib-basemap

Basemap with joint histograms plot


Here is the code returning the figure below:

import seaborn as sns

plt.figure(figsize=(8, 8))
gs = plt.GridSpec(3, 3)
ax_main = plt.subplot(gs[1:3, :2])
ax_lon = plt.subplot(gs[0, :2])
ax_lat = plt.subplot(gs[1:3, 2])

m = Basemap(projection='merc', resolution='i', llcrnrlon=llcrnrlon, llcrnrlat=llcrnrlat,
            urcrnrlon=urcrnrlon, urcrnrlat=urcrnrlat, ax=ax_main)

try:
    m.drawcoastlines(linewidth=0.5)
except:
    pass
m.drawcountries()
m.drawmapboundary()

lon_range = urcrnrlon - llcrnrlon
lat_range = urcrnrlat - llcrnrlat
lat_interval = round(0.2 * lat_range, 2)
lon_interval = round(0.2 * lon_range, 2)

parallels = np.arange(-90, 90, lat_interval)
meridians = np.arange(-180, 180, lon_interval)
m.drawparallels(parallels, labels=[0, 1, 0, 0], fontsize=8, dashes=[1, 5])
m.drawmeridians(meridians, labels=[0, 0, 1, 0], fontsize=8, dashes=[1, 5])
m.fillcontinents(color='#F0F0F0', lake_color='#F0F0F0')

x1, y1 = m(LON_1, LAT_1)
x2, y2 = m(LON_2, LAT_2)

ax_main.scatter(x1, y1, c='steelblue', alpha=.8,  s=8, label='Data 1')
ax_main.scatter(x2, y2, c='red', alpha=.8, s=8, label='Data 2')
ax_main.set_xlabel('Longitude', fontsize=12, labelpad=10)
ax_main.set_ylabel('Latitude', fontsize=12, labelpad=10)
ax_main.legend(loc='upper right') 

# Histogram of longitude
ax_lon.hist(LON_1, bins=20, histtype='step', density=True, color='steelblue', 
            align='mid', label='Data 1')
ax_lon.hist(LON_2, bins=20, histtype='step', density=True, color='red', 
            align='mid', label='Data2')
ax_lon.set_xticks([]) 
ax_lon.set_yticks([]) 
#ax_lon.legend(loc='upper right')

# Histogram of latitude
ax_lat.hist(LAT_1, bins=20, histtype='step', density=True, color='steelblue', align='mid', 
            orientation='horizontal', label='Data 1')
ax_lat.hist(LAT_2, bins=20, histtype='step', density=True, color='red', align='mid', 
            orientation='horizontal', label='Data 2')
# ax_lat.tick_params(axis='y', rotation=-90)
ax_lat.set_yticks([]) 
ax_lat.set_xticks([]) 
#ax_lat.legend(loc='upper right')

plt.tight_layout()
plt.show()

enter image description here

I cannot adjust the histograms' size with sharex/sharey option otherwise they are not properly displayed (do not know why, but I think it has to do with Basemap). Is there a way to adjust the top histogram size such that its x-axis (y-axis) is the same as the x-axis (y-axis) of the map (right histo)?


Solution

  • The top and right (axes) frames need to adopt the related dimensions/positions of the main axes. The commands .get_position() and .set_position() are used to operate and get the required plots.

    from mpl_toolkits.basemap import Basemap
    import matplotlib.pyplot as plt
    import numpy as np
    import seaborn as sns
    
    plt.figure(figsize=(8, 8-2))
    gs = plt.GridSpec(3, 3)
    ax_main = plt.subplot(gs[1:3, :2]) # lower-left
    ax_lon = plt.subplot(gs[0, :2])    # top
    ax_lat = plt.subplot(gs[1:3, 2])   # lower-right
    
    llcrnrlon=15.5
    llcrnrlat=39.5
    urcrnrlon=16.5
    urcrnrlat=40
    
    m = Basemap(projection='merc', resolution='i', llcrnrlon=llcrnrlon, llcrnrlat=llcrnrlat,
                urcrnrlon=urcrnrlon, urcrnrlat=urcrnrlat, ax=ax_main)
    
    try:
        m.drawcoastlines(linewidth=0.5)
    except:
        pass
    #m.drawcountries()
    m.drawmapboundary()
    
    lon_range = urcrnrlon - llcrnrlon
    lat_range = urcrnrlat - llcrnrlat
    lat_interval = round(0.2 * lat_range, 2)
    lon_interval = round(0.2 * lon_range, 2)
    
    parallels = np.arange(-90, 90, lat_interval)
    meridians = np.arange(-180, 180, lon_interval)
    m.drawparallels(parallels, labels=[1, 0, 0, 0], fontsize=8, dashes=[1, 5])
    m.drawmeridians(meridians, labels=[0, 0, 1, 0], fontsize=8, dashes=[1, 5])
    m.fillcontinents(color='#F0F0F0', lake_color='#F0F0F0')
    
    # Adjust frames' positions
    # get dimensions of the 2 axes
    mbox = ax_main.get_position() #_main
    box1 = ax_lon.get_position()  # top
    box2 = ax_lat.get_position()  # right, needs both LL & UR
    
    # current dimensions
    w0,h0 = mbox.x1-mbox.x0, mbox.y1-mbox.y0
    w1,h1 = box1.x1-box1.x0, box1.y1-box1.y0
    w2,h2 = box2.x1-box2.x0, box2.y1-box2.y0
    
    # compute/apply new positions for top-frame
    new_w1 = w0              # adopt main's w0
    new_h1 = h1/w1*w0        # adjust h, retaining w/h ratio
    ax_lon.set_position([box1.x0, box1.y0, new_w1, new_h1])
    
    # compute/apply new positions for right frame
    new_h2 = h0                    # adopt main's h0
    new_w2 = w2/h2 * new_h2        # adjust w, retaining w/h ratio
    right_LL = [box2.x0, mbox.y0]  # align LL horizontally
    
    # finally, apply new position to the right frame
    ax_lat.set_position([right_LL[0], right_LL[1], new_w2, new_h2])
    

    fig1