2015-05-04 35 views
9

Utilizzando Cartopy, mi piacerebbe avere il controllo completo su dove va la mia barra colori. Di solito lo faccio posizionando gli assi correnti come base e quindi creando nuovi assi per la barra dei colori. Funziona bene per assi standard matplotlib ma non quando si utilizza Cartopy e geo_axes, perché ciò distorce gli assi.Posizionamento corretto della barra dei colori relativa agli assi geografici (cartopy)

Quindi, la mia domanda è: come ottengo la posizione esatta dei miei geo_axes?

Ecco un esempio di codice in base ai documenti Cartopy http://scitools.org.uk/cartopy/docs/latest/matplotlib/advanced_plotting.html:

import cartopy.crs as ccrs 
import matplotlib.pyplot as plt 
import os 
from netCDF4 import Dataset as netcdf_dataset 
from cartopy import config 

def main(): 
    fname = os.path.join(config["repo_data_dir"], 
        'netcdf', 'HadISST1_SST_update.nc' 
        ) 

    dataset = netcdf_dataset(fname) 

    sst = dataset.variables['sst'][0, :, :] 
    lats = dataset.variables['lat'][:] 
    lons = dataset.variables['lon'][:] 

    #my preferred way of creating plots (even if it is only one plot) 
    ef, ax = plt.subplots(1,1,figsize=(10,5),subplot_kw={'projection': ccrs.PlateCarree()}) 
    ef.subplots_adjust(hspace=0,wspace=0,top=0.925,left=0.1) 

    #get size and extent of axes: 
    axpos = ax.get_position() 
    pos_x = axpos.x0+axpos.width + 0.01# + 0.25*axpos.width 
    pos_y = axpos.y0 
    cax_width = 0.04 
    cax_height = axpos.height 
    #create new axes where the colorbar should go. 
    #it should be next to the original axes and have the same height! 
    pos_cax = ef.add_axes([pos_x,pos_y,cax_width,cax_height]) 

    im = ax.contourf(lons, lats, sst, 60, transform=ccrs.PlateCarree()) 

    ax.coastlines() 

    plt.colorbar(im, cax=pos_cax) 

    ax.coastlines(resolution='110m') 
    ax.gridlines() 
    ax.set_extent([-20, 60, 33, 63]) 

    #when using this line the positioning of the colorbar is correct, 
    #but the image gets distorted. 
    #when omitting this line, the positioning of the colorbar is wrong, 
    #but the image is well represented (not distorted). 
    ax.set_aspect('auto', adjustable=None) 

    plt.savefig('sst_aspect.png') 
    plt.close() 



if __name__ == '__main__': main() 

risultanti figura, quando si utilizza "set_aspect": enter image description here

risultante figura, quando omettendo "set_aspect": enter image description here

Fondamentalmente, mi piacerebbe ottenere la prima cifra (colorbar posizionata correttamente) ma senza usare "set_aspect". Immagino che questo dovrebbe essere possibile con alcune trasformazioni, ma non ho trovato una soluzione finora.

Grazie!

risposta

12

Ottima domanda! Grazie per il codice e le immagini, rende il problema molto più facile da capire e rende più facile una rapida iterazione sulle possibili soluzioni.

Il problema qui è essenzialmente uno matplotlib. Cartopy chiama ax.set_aspect('equal') in quanto fa parte delle unità cartesiane della definizione di una proiezione.

La funzionalità di proporzioni uguali di Matplotlib riduce gli assi in modo che corrispondano ai limiti x e y, invece di modificare i limiti per adattarsi al rettangolo degli assi. È per questo motivo che gli assi non riempiono lo spazio ad esso assegnato sulla figura. Se ridimensiona la figura in modo interattivo, vedrai che la quantità di spazio occupata dagli assi varia a seconda dell'aspetto che ridimensiona la tua figura.

Il modo più semplice per identificare la posizione di un asse è con il metodo ax.get_position() che si è già utilizzato. Tuttavia, come ora sappiamo, questa "posizione" cambia con la dimensione della figura. Una soluzione è quindi di ricalcolare la posizione della barra di colore ogni volta che la cifra viene ridimensionata.

Il matplotlib event machinery ha un "resize_event" che viene attivato ogni volta che una figura viene ridimensionata. Se usiamo questo macchinario per la vostra barra colorata, il nostro evento potrebbe essere simile:

def resize_colobar(event): 
    # Tell matplotlib to re-draw everything, so that we can get 
    # the correct location from get_position. 
    plt.draw() 

    posn = ax.get_position() 
    colorbar_ax.set_position([posn.x0 + posn.width + 0.01, posn.y0, 
          0.04, axpos.height]) 

fig.canvas.mpl_connect('resize_event', resize_colobar) 

Quindi, se ci rapportiamo questo torna a cartopy, e la tua domanda originale, è ora possibile modificare le dimensioni della barra colorata in base alla posizione di le geo-assi. Il codice completo per fare questo potrebbe essere simile:

import cartopy.crs as ccrs 
import matplotlib.pyplot as plt 
import os 
from netCDF4 import Dataset as netcdf_dataset 
from cartopy import config 


fname = os.path.join(config["repo_data_dir"], 
       'netcdf', 'HadISST1_SST_update.nc' 
       ) 
dataset = netcdf_dataset(fname) 
sst = dataset.variables['sst'][0, :, :] 
lats = dataset.variables['lat'][:] 
lons = dataset.variables['lon'][:] 

fig, ax = plt.subplots(1, 1, figsize=(10,5), 
         subplot_kw={'projection': ccrs.PlateCarree()}) 

# Add the colorbar axes anywhere in the figure. Its position will be 
# re-calculated at each figure resize. 
cbar_ax = fig.add_axes([0, 0, 0.1, 0.1]) 

fig.subplots_adjust(hspace=0, wspace=0, top=0.925, left=0.1) 

sst_contour = ax.contourf(lons, lats, sst, 60, transform=ccrs.PlateCarree()) 


def resize_colobar(event): 
    plt.draw() 

    posn = ax.get_position() 
    cbar_ax.set_position([posn.x0 + posn.width + 0.01, posn.y0, 
          0.04, posn.height]) 

fig.canvas.mpl_connect('resize_event', resize_colobar) 

ax.coastlines() 

plt.colorbar(sst_contour, cax=cbar_ax) 


ax.gridlines() 
ax.set_extent([-20, 60, 33, 63]) 

plt.show() 
+0

Ottima risposta! Non avrei mai trovato questa soluzione da sola! – user3497890

+0

Forse una breve domanda se posso: È possibile inserire la definizione di resize_colorbar da qualche altra parte e passare axe e cbar_ax come argomenti (in modo che questa funzione diventi riutilizzabile)? Aggiornamento – user3497890

+0

: mentre plt.show() fornisce il risultato corretto, plt.savefig ('sst.png') no (la barra colorata rimane nella posizione originale in [0,0,0,1,0,1]). Ho cercato di cambiare il back-end in matplotlib.use ('Agg'), ma questo non aiuta. Qualche idea su come potrei farlo funzionare con savefig? – user3497890

0

Tenendo presente che mpl_toolkits.axes_grid1 non è essere la parte migliore testata di matplotlib, siamo in grado di utilizzare sia funzionalità per ottenere quello che vuoi.

possiamo usare il Example data nella documentazione mpl_toolkits, ma le axes_class deve essere impostato in modo esplicito, deve essere impostato come axes_class=plt.Axes, altrimenti tenta di creare un GeoAxes come colorbar

import numpy as np 
import matplotlib.pyplot as plt 
from mpl_toolkits.axes_grid1 import make_axes_locatable 

def sample_data_3d(shape): 
    """Returns `lons`, `lats`, and fake `data` 

    adapted from: 
    http://scitools.org.uk/cartopy/docs/v0.15/examples/axes_grid_basic.html 
    """ 


    nlons, nlats = shape 
    lats = np.linspace(-np.pi/2, np.pi/2, nlats) 
    lons = np.linspace(0, 2 * np.pi, nlons) 
    lons, lats = np.meshgrid(lons, lats) 
    wave = 0.75 * (np.sin(2 * lats) ** 8) * np.cos(4 * lons) 
    mean = 0.5 * np.cos(2 * lats) * ((np.sin(2 * lats)) ** 2 + 2) 

    lats = np.rad2deg(lats) 
    lons = np.rad2deg(lons) 
    data = wave + mean 

    return lons, lats, data 


# get data 
lons, lats, data = sample_data_3d((180, 90)) 


# set up the plot 
proj = ccrs.PlateCarree() 

f, ax = plt.subplots(1, 1, subplot_kw=dict(projection=proj)) 
h = ax.pcolormesh(lons, lats, data, transform=proj, cmap='RdBu') 
ax.coastlines() 

# following https://matplotlib.org/2.0.2/mpl_toolkits/axes_grid/users/overview.html#colorbar-whose-height-or-width-in-sync-with-the-master-axes 
# we need to set axes_class=plt.Axes, else it attempts to create 
# a GeoAxes as colorbar 

divider = make_axes_locatable(ax) 
ax_cb = divider.new_horizontal(size="5%", pad=0.1, axes_class=plt.Axes) 


f.add_axes(ax_cb) 
plt.colorbar(h, cax=ax_cb) 

Colorbar with make_axes_locatable

Nota anche il cartopy example che utilizza AxesGrid da mpl_toolkits.axes_grid1.