2015-05-04 10 views
9

Mit Cartopy möchte ich die volle Kontrolle darüber haben, wohin meine Farbleiste geht. Normalerweise mache ich das, indem ich die aktuelle Achsenposition als Basis erhalte und dann neue Achsen für die Farbleiste erstelle. Dies funktioniert gut für Standard-Matplotlib-Achsen, aber nicht bei Verwendung von Cartopy und Geo-Achsen, da dies die Achsen verzerrt.Korrekte Platzierung der Farbleiste relativ zu Geoachsen (Cartopy)

Also, meine Frage ist: Wie bekomme ich die genaue Position meiner Geo_axe?

Hier ist ein Code-Beispiel für die Dokumentation http://scitools.org.uk/cartopy/docs/latest/matplotlib/advanced_plotting.html Cartopy basiert:

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() 

Bild ergibt, wenn "set_aspect" verwendet: enter image description here

so erhaltene, als "set_aspect" Weglassen: enter image description here

Grundsätzlich möchte ich die erste Figur (korrekt platzierte Farbleiste) erhalten, aber ohne den "set_aspect" zu verwenden. Ich denke, das sollte mit einigen Transformationen möglich sein, aber ich habe bisher keine Lösung gefunden.

Danke!

Antwort

12

Große Frage! Dank des Codes und der Bilder ist das Problem viel einfacher zu verstehen und es ist einfacher, schnell auf mögliche Lösungen zu iterieren.

Das Problem hier ist im Wesentlichen eine Matplotlib. Cartopy ruft ax.set_aspect('equal') auf, da dies Teil der kartesischen Einheiten einer Definition einer Projektion ist.

Die Funktion des gleichen Seitenverhältnisses von Matplotlib ändert die Größe der Achsen so, dass sie den X- und Y-Grenzen entspricht, anstatt die Grenzen so zu ändern, dass sie in das Achsenrechteck passen. Aus diesem Grund füllen die Achsen nicht den zugewiesenen Platz auf der Figur. Wenn Sie die Größe der Grafik interaktiv ändern, sehen Sie, dass die Größe des von den Achsen belegten Platzes von dem Aspekt abhängt, an dem Sie die Größe ändern.

Die einfachste Möglichkeit, die Position einer Achse zu ermitteln, ist die Methode ax.get_position(), die Sie bereits verwendet haben. Wie wir jedoch wissen, ändert sich diese "Position" mit der Größe der Figur. Eine Lösung besteht daher darin, die Position der Farbleiste jedes Mal neu zu berechnen, wenn die Größe der Figur geändert wird.

Die matplotlib event machinery hat einen "resize_event", der jedes Mal ausgelöst wird, wenn die Größe einer Figur geändert wird. Wenn wir diese Maschinen für Ihre colorbar verwenden, kann unsere Veranstaltung in etwa so aussehen:

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) 

Also, wenn wir diese zurück zu cartopy beziehen, und Ihre ursprüngliche Frage, ist es nun möglich ist, die colorbar basierend auf der Position der Größe ändern die Geo-Achsen. Der vollständige Code, dies zu tun könnte wie folgt aussehen:

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

Große Antwort! Ich hätte diese Lösung nie alleine gefunden! – user3497890

+0

Vielleicht eine kurze Nebenfrage, wenn ich darf: Ist es möglich, die Definition von resize_colorbar woanders zu setzen und ax und cbar_ax als Argumente zu übergeben (damit diese Funktion wiederverwendbar wird)? – user3497890

+0

Update: Während plt.show() das korrekte Ergebnis liefert, bleibt plt.savefig ('sst.png') (Farbbalken bleibt bei [0,0,0,1,0,1] an der ursprünglichen Position). Versucht, das Backend zu matplotlib.use ('Agg') zu ändern, aber das hilft nicht. Irgendeine Idee, wie ich es mit Savegig zum Laufen bringen könnte? – user3497890

0

Bedenkt man, dass mpl_toolkits.axes_grid1 ist nicht die am besten getestete Teil matplotlib, können wir es Funktionalität erreichen verwenden, was Sie wollen.

Wir können die Example in der mpl_toolkits Dokumentation angegeben verwenden, aber die axes_class Bedürfnisse explizit festgelegt werden, als axes_class=plt.Axes eingestellt werden muss, sonst wird versucht, eine GeoAxes als colorbar erstellen

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

Beachten Sie auch die Cartopy example, die AxesGrid von mpl_toolkits.axes_grid1 verwendet.