2015-05-04 8 views
9

Korzystając z kartonu, chciałbym mieć pełną kontrolę nad tym, gdzie znajduje się mój pasek kolorów. Zwykle robię to, ustawiając aktualną pozycję osi, a następnie tworzę nowe osie dla paska kolorów. Działa to dobrze w standardowych osiach matplotlib, ale nie w przypadku użycia Cartopy i geo_axes, ponieważ spowoduje to zniekształcenie osi.Prawidłowe umiejscowienie paska kolorów względem osi geograficznych (koszyk)

Moje pytanie brzmi: jak uzyskać dokładną pozycję moich geo_ax?

Oto przykład kodu na podstawie Dokumenty 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() 

wynikową, przy użyciu "set_aspect": enter image description here

wynikową gdy pominięcie "set_aspect": enter image description here

Zasadniczo chciałbym uzyskać pierwszą figurę (poprawnie umieszczony pasek kolorów), ale bez użycia "set_aspect". Myślę, że powinno to być możliwe z pewnymi transformacjami, ale nie znalazłem dotychczas rozwiązania.

Dzięki!

Odpowiedz

12

Świetne pytanie! Dzięki temu, że kod i zdjęcia sprawiają, że problem jest dużo łatwiejszy do zrozumienia, a także łatwiej jest szybko dokonać iteracji na temat możliwych rozwiązań.

Problem tutaj jest zasadniczo matplotlib jeden. Kartografia nazywa się ax.set_aspect('equal'), ponieważ jest to część kartezjańskich jednostek definicji projekcji.

Funkcja równego współczynnika proporcji Matplotlib zmienia rozmiar osi, aby dopasować je do granic x i y, zamiast zmieniać limity w celu dopasowania do prostokąta osi. Z tego powodu osie nie wypełniają przestrzeni przydzielonej mu na rysunku. Jeśli interaktywnie zmienisz rozmiar rysunku, zobaczysz, że ilość miejsca zajmowanego przez osie różni się w zależności od tego, do którego rozmiaru zmienisz figurę.

Najprostszym sposobem identyfikacji położenia osi jest metoda, której już używasz. Jednak, jak już wiemy, ta "pozycja" zmienia się wraz z wielkością figury. Jednym z rozwiązań jest więc ponowne obliczenie położenia paska kolorów za każdym razem, gdy wielkość jest zmieniana.

Parametr matplotlib event machinery ma wartość "resize_event", która jest wyzwalana przy każdej zmianie rozmiaru figury. Jeśli użyjemy tej maszyny do colorbar nasza impreza może wyglądać mniej więcej tak:

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) 

Jeśli więc odnosić się to z powrotem do cartopy i oryginalne pytanie, jest obecnie możliwe, aby zmienić rozmiar colorbar na podstawie pozycji geo-topory. Pełny kod to zrobić może wyglądać następująco:

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

Świetna odpowiedź! Nigdy nie znalazłbym tego rozwiązania na własną rękę! – user3497890

+0

Może krótkie pytanie boczne, jeśli mogę: Czy możliwe jest umieszczenie definicji resize_colorbar gdzie indziej i przekazanie ax i cbar_ax jako argumentów (aby ta funkcja stała się wielokrotnego użytku)? – user3497890

+0

Aktualizacja: podczas gdy plt.show() dostarcza poprawnego wyniku, plt.savefig ('sst.png') nie ma (kolor paska pozostaje w pierwotnej pozycji przy [0,0,0.1,0.1]). Próbowałem zmienić backend na matplotlib.use ('Agg'), ale to nie pomaga. Jakiś pomysł, jak mogę go uruchomić z savefig? – user3497890

0

Mając na uwadze, że mpl_toolkits.axes_grid1 nie być najlepszym przetestowane część matplotlib, możemy wykorzystać jego funkcjonalność, aby osiągnąć to, co chcesz.

Możemy użyć Example podane w dokumentacji mpl_toolkits, ale axes_class musi być ustawiona jawnie, to musi być ustawiony jako axes_class=plt.Axes, jeszcze próbuje stworzyć GeoAxes jako 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

Należy również odnotować kartotekę example, która używa AxesGrid z mpl_toolkits.axes_grid1.

Powiązane problemy