how to adjust space between columns in xarray faceted plot with basemap? - python

I want to make a faceted plot using xarray. The problem occurs when I try to add a bassemap to each subplot using basemap. Too much space is put between columns. I have already tried plt.subplots_adjust(hspace=0.01, wspace=0) and plt.tight_layout(pad=0), but no luck yet. How can I control the space between columns?
The sample dataset (ds_sub) can be found here. The following code produces the below figure.
Thanks.
import pickle
from matplotlib import pyplot
from matplotlib import cm, colors
from mpl_toolkits.basemap import Basemap
import xarray as xr
with open('/data/direcotory/test_data.pkl', "rb") as f:
ds_sub = pickle.load(f)
f.close()
plt.close("all")
plt.figure()
levels = np.arange(0, ds_sub['sum'].quantile(0.99),
(ds_sub['sum'].quantile(0.99) / 10)).tolist()
norm = colors.BoundaryNorm(levels, len(levels))
p = ds_sub['sum'].loc[[0, 3, 6, 9], :, :].plot(add_colorbar=False, row='time', cmap='OrRd', col_wrap=2,
norm=norm)
mappable = p.axes[0][0].collections[0]
cax = plt.axes([0.85, 0.2, 0.05, 0.6])
cbar1 = plt.colorbar(mappable, ticks=levels,
values=levels, cax=cax, orientation='vertical')
cbar1.ax.tick_params(labelsize=14)
for i, ax in enumerate(p.axes.flatten()):
ax.set_xlabel('')
ax.set_ylabel('')
ax.margins(0, 0)
lon_0 = ds_sub['lon'].mean()
lat_0 = ds_sub['lat'].mean()
m = Basemap(resolution='f',
lat_ts=20, lat_0=lat_0, lon_0=lon_0,
llcrnrlon=ds_sub['lon'].min(),
llcrnrlat=ds_sub['lat'].min(),
urcrnrlon=ds_sub['lon'].max(),
urcrnrlat=ds_sub['lat'].max(), ax=ax)
m.drawmapboundary()
m.drawrivers()
m.drawcoastlines()
m.drawcountries()
m.drawstates()
dohax, dohay = m(51.534817, 25.286106)
print(i)
plt.subplots_adjust(hspace=0.01, wspace=0)
plt.tight_layout(pad=0)
plt.show()

Related

Change colorbar ticks from powers of 10 to plain numbers

I am trying to read an .nc file and display the data on a map. I want the colorbar ticks to be not in powers of 10 scale, but rather in plain numbers, so from 0.1 to 10. Moreover, it will be welcome if I can format it so it goes from 0.1 to 10 in like 7 ticks, so the result is the same as in the attached picture.
Please note that I have not added the code snippet related to the data downloading, so the script is not runnable. If you cannot spot the mistake without running the code, please let me know, I will attach it so you can download the .nc file.
Here is the code I am using. Sorry for the redundant imports.
import xarray as xr
import cartopy
import matplotlib
from matplotlib.colors import LogNorm
from matplotlib.offsetbox import AnchoredText
from matplotlib.ticker import ScalarFormatter, FormatStrFormatter
import sys
import os
#First we open the dataset and read the varaible of interest
ds = xr.open_dataset(OUTPUT_FILENAME)
chl = ds.CHL.sel(time=min_date)
#Setting the figure size and projection
fig = plt.figure(figsize=(15,15))
ax = plt.axes(projection=ccrs.PlateCarree())
#Adding coastlines and land
ax.coastlines(resolution="10m") #Coastline resolution
ax.set_extent([-9,2,35,37.6]) #Map extent
ax.add_feature(cartopy.feature.LAND) #Adding land
#Formatting colorbar <---------------------------------------------------DOES NOT WORK
ax.ticklabel_format(style='plain',useMathText=None)
#Adding gridlines
gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
linewidth=0.5, color='black', alpha=0.7, linestyle='--')
gl.top_labels = False
gl.right_labels = False
#Adding lat/lon labels in the axes
ax.text(-0.07, 0.55, 'Latitude [deg]', va='bottom', ha='center',
rotation='vertical', rotation_mode='anchor',
transform=ax.transAxes)
ax.text(0.5, -0.2, 'Longitude [deg]', va='bottom', ha='center',
rotation='horizontal', rotation_mode='anchor',
transform=ax.transAxes)
#Adding (C) info to the figure
SOURCE = 'ICMAN CSIC'
text = AnchoredText('$\copyright$ {}'.format(SOURCE),
loc=1, prop={'size': 9}, frameon=True)
ax.add_artist(text)
#Drawing the plot
chl.plot(ax=ax, transform=ccrs.PlateCarree(),
vmin=0.1, vmax=10, extend='both', cbar_kwargs={'shrink': 0.2, 'pad':0.01},
cmap="jet", norm=LogNorm(vmax=10))
#Figure title
ax.set_title("Chlorophyll NN (mg/m$^{3}$) "+ min_date_h + " - " + max_date_h)
The colorbar should be the last axes in the figure (fig.axes[-1]).
You can manually set the colorbar's ticks and tick labels:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
X, Y = np.mgrid[-3:3:100j, -2:2:100j]
Z = 10*np.exp(-X**2 - Y**2)
fig, ax = plt.subplots()
pcm = ax.pcolor(X, Y, Z, norm=colors.LogNorm(vmin=.1, vmax=10), cmap='jet')
fig.colorbar(pcm, ax=ax)
cb = fig.axes[-1]
ticks = [.1,.2,.5,1,2,5,10]
cb.yaxis.set_ticks(ticks, labels=[f"{t:g}" for t in ticks])
cb.minorticks_off()
(prior to matplotlib 3.5.0 you must set ticks and labels separately).

How to rotate a Subplot by 45 degree in Matplotlib?

I am trying to explore a subplot 2 plots with square in shape rotated by 45 degree.
import matplotlib.pyplot as plt
from matplotlib import colors
import numpy as np
data = np.random.rand(10, 10) * 20
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
fig, ax= plt.subplots(1,2)
ax[0].imshow(data, cmap=cmap, norm=norm)
# draw gridlines
ax[0].grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax[0].set_xticks(np.arange(-.5, 10, 1));
ax[0].set_yticks(np.arange(-.5, 10, 1));
ax[1].imshow(data, cmap=cmap, norm=norm)
# draw gridlines
ax[1].grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax[1].set_xticks(np.arange(-.5, 10, 1));
ax[1].set_yticks(np.arange(-.5, 10, 1));
plt.show()
Actual Result is :-
I want to rotate individual plot by 45 degree. Something like:-
I am trying to find in Matplotlib Documentation. Still not getting. Any help?
Please note this is NOT DUPLICATE OF
Is there a way to rotate a matplotlib plot by 45 degrees?
The mentioned URL is for a plot. and the solution is to rotate IMAGE. However this is pertaining to Subplot. I want to rotate PLOT not image as whole.
Based on this link and documentation about floating_axes, you can try something like this:
from mpl_toolkits.axisartist.grid_finder import DictFormatter
import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D
import mpl_toolkits.axisartist.floating_axes as floating_axes
from matplotlib import colors
import numpy as np
def setup_axes1(fig, rect, angle):
tr = Affine2D().scale(2, 2).rotate_deg(angle)
#We create dictionarys to keep the xticks and yticks after the rotation
dictio={i:str(val) for i,val in enumerate(np.arange(-.5, 10, 1).tolist())}
reversedictio={i:dictio[val] for i,val in enumerate(list(reversed(sorted(dictio.keys()))))}
grid_helper = floating_axes.GridHelperCurveLinear(
tr, extremes=(-0.5, 9.5,-0.5, 9.5), tick_formatter1= DictFormatter(dictio),
tick_formatter2=DictFormatter(reversedictio))
ax1 = floating_axes.FloatingSubplot(fig, rect, grid_helper=grid_helper)
fig.add_subplot(ax1)
aux_ax = ax1.get_aux_axes(tr)
grid_helper.grid_finder.grid_locator1._nbins = 10 #Number of rows
grid_helper.grid_finder.grid_locator2._nbins = 10 #Number of columns
return aux_ax
fig1, axes=plt.subplots(2,figsize=(20,20))
plt.rcParams.update({'font.size': 27})
#We erase the first previous axes
fig1.delaxes(axes[0])
fig1.delaxes(axes[1])
data = np.random.rand(10, 10) * 20
#We create the floating_axes
ax0 = setup_axes1(fig1, 121,-45)
ax1 = setup_axes1(fig1, 122,-45)
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
ax0.imshow(data, cmap=cmap, norm=norm,interpolation="nearest")
# draw gridlines
ax0.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax1.imshow(data, cmap=cmap, norm=norm,interpolation="nearest")
# draw gridlines
ax1.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
plt.show()
Output:
Or, as an other alternative, I found a "tricky" way to do it, and it's about catching the figures in the buffer, rotate them -45 degrees, and then merge them into a single image, and since you have the same two images, you can try something like this:
import matplotlib
import io
from PIL import Image
import matplotlib.pyplot as plt
from matplotlib import colors
import numpy as np
##PLOTING THE FIGURE##
data = np.random.rand(10, 10) * 20
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
#We change style values to get the image with better quality
plt.rcParams.update({'font.size': 46})
plt.figure(figsize=(20,20))
plt.imshow(data, cmap=cmap, norm=norm)
# draw gridlines
plt.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
plt.gca().set_xticks(np.arange(-.5, 10, 1));
plt.gca().set_yticks(np.arange(-.5, 10, 1));
##SAVING THE FIGURE INTO AN IMAGE##
#We save the current figure as a Image
buf = io.BytesIO()
plt.savefig(buf, format='png',bbox_inches='tight')
buf.seek(0)
im = Image.open(buf) #We open the current image saved in the buffer
#We rotate the image and fill the background with white
img_01=im.rotate(-45, Image.NEAREST, expand = 1, fillcolor = (255,255,255))
buf.close()
##MERGING THE TWO FIGURES##
new_im = Image.new('RGB', (2*img_01.size[0]+20,img_01.size[1]), 'white')
mouse_mask = img_01.convert('RGBA')
new_im.paste(img_01, (0,0))
new_im.paste(img_01, (img_01.size[0]+8,0))
new_im.save("merged_images.png", 'PNG') #Important(just to clarify): save the image, since the buffer is renewed every time you run the script
new_im.show()
Output:
I helped myself with these links:
How-to-merge-images-with-same-size-using-the-python-3-module-pillow
how-to-save-a-pylab-figure-into-in-memory-file-which-can-be-read-into-pil-image
python-pillow-rotate-image-90-180-270-degrees
specify-image-filling-color-when-rotating-in-python-with-pil-and-setting-expand

Animate a plot over an image in Python

I am trying to animate a plot of geographic coordinates over an image, in this case a snippet of a map. I've managed to produce a static plot but cannot get it to animate. I've tried to animate using the matplotlib animation function, but haven't had any success with it. I am using pandas to read the csv in to Python and matplotlib.pyplot to plot. Below is the code for the static plot.
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('mydata.csv', header=0)
# find max/min, plug into a website, snip area as png and insert as plotmap
BBox = ((df.LONGITUDE.min(), df.LONGITUDE.max(),
df.LATITUDE.min(), df.LATITUDE.max()))
#read the image in, plot points over image
plotmap = "myimage.png"
truthplot = plt.imread(plotmap)
fig, ax = plt.subplots(figsize = (8,8),linewidth = 0.1)
ax.scatter(df.LONGITUDE, df.LATITUDE, zorder=1, alpha= 0.5, c='b', s=10)
plottitle = "test"
ax.set_title(plottitle)
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
ax.set_xlim(BBox[0], BBox[1])
ax.set_ylim(BBox[2], BBox[3])
ax.imshow(truthplot, zorder=0, extent = BBox, aspect= 'equal')
plt.show()
Some example coordinates:
LATITUDE LONGITUDE
30.112342 10.678982
29.443459 11.678997
29.334221 11.889544
28.993448 12.003847
I'm still a newbie; any help is appreciated.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
df = pd.read_csv('mydata.csv', header=0)
# find max/min, plug into a website, snip area as png and insert as plotmap
BBox = ((df.LONGITUDE.min(), df.LONGITUDE.max(),
df.LATITUDE.min(), df.LATITUDE.max()))
#read the image in, plot points over image
plotmap = "myimage.png"
truthplot = plt.imread(plotmap)
fig, ax = plt.subplots(figsize = (8,8),linewidth = 0.1)
plottitle = "test"
ax.set_title(plottitle)
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
ax.set_xlim(BBox[0], BBox[1])
ax.set_ylim(BBox[2], BBox[3])
scat = ax.scatter(df.LONGITUDE, df.LATITUDE, zorder=1, alpha= 0.5, c='b', s=10)
color_data = np.random.random((500, len(df.LATITUDE)))
def update(frame):
scat.set_array(color_data[frame])
return scat,
ani = FuncAnimation(fig, update, frames=range(500), blit=True)
ax.imshow(truthplot, zorder=0, extent = BBox, aspect= 'equal')
plt.show()
I am not sure what you wanted to be animated, that's why I just made the points blink.
But you can easily change your scatter plot all you want in the update function.
scat is a PathCollection, its functions you can find here:
https://matplotlib.org/3.2.1/api/collections_api.html#matplotlib.collections.PathCollection
UPDATE
If you want to build the path step by step, manipulation the PathCollention is not very convenient. I would recommend recreating the obj.
scat = ax.scatter(df.LONGITUDE[0], df.LATITUDE[0], zorder=1, alpha= 0.5, c='b', s=10)
max_frames = 10
def update(frame):
scat = ax.scatter(df.LONGITUDE[:(frame * (len(df.LONGITUDE) + 1))//max_frames],
df.LATITUDE[:(frame * (len(df.LATITUDE) + 1))//max_frames],
zorder=1, alpha= 0.5, c='b', s=10)
return scat,
You can try this, if you want to use a scatter plot on a DataFrame directly:
BBox = ((df['LONGITUDE'].min(), df['LONGITUDE'].max(),
df['LATITUDE'].min(), df['LATITUDE'].max()))
plotmap = 'myimage.png'
truthplot = plt.imread(plotmap)
ax = df.plot.scatter(x='LONGITUDE', y='LATITUDE', c='Red')
plottitle = "test"
ax.set_title(plottitle)
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
ax.set_xlim(BBox[0], BBox[1])
ax.set_ylim(BBox[2], BBox[3])
ax.imshow(truthplot, zorder=0, extent = BBox, aspect= 'equal')
plt.show()

How to easily add a sub_axes with proper position and size in matplotlib and cartopy?

I want to add a 2nd axes at the top right corner of a 1st axes. After googling, I found two ways to do things like this: fig.add_axes(), and mpl_toolkits.axes_grid.inset_locator.inset_axes. But the fig.add_axes() doesn't accept transform arg. So the following code throws an error. So the position can't be under the parent axes coordinates but the figure coordinates.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig, ax = plt.subplots(1, 1, subplot_kw={'projection': ccrs.PlateCarree()})
ax2 = fig.add_axes([0.8, 0, 0.2, 0.2], transform=ax.transAxes, projection=ccrs.PlateCarree())
And inset_axes() doesn't accept the projection arg, so I can't add ax2 as a cartopy geo-axes.
from mpl_toolkits.axes_grid.inset_locator import inset_axes
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig, ax = plt.subplots(1, 1, subplot_kw={'projection': ccrs.PlateCarree()})
# The following line doesn't work
ax2 = inset_axes(ax, width='20%', height='20%', axes_kwargs={'projection': ccrs.PlateCarree()})
# Doesn't work neither:
ax2 = inset_axes(ax, width='20%', height='20%', projection=ccrs.PlateCarree())
I've asked the question at matplotlib issue. It seems the following code works well as long as it's not a cartopy axes.
import matplotlib as mpl
fig, ax = plt.subplots(1, 1)
box = mpl.transforms.Bbox.from_bounds(0.8, 0.8, 0.2, 0.2)
ax2 = fig.add_axes(fig.transFigure.inverted().transform_bbox(ax.transAxes.transform_bbox(box)))
Question:
How to easily add a sub_axes with proper position and size in matplotlib and cartopy?
As I understand, after ax.set_extend(), the size of axes will change. So maybe is there a way that some point of sub_axes (eg: top right corner of ax2) can be anchored at one fixed position of the parent_axes (eg: top right corner of ax1)?
As inset_axes() doesn't accept projection arg, the roundabout way is to use InsetPosition(). This way you can create an axes in the usual way (using projection), and then "link" both axes using InsetPosition(). The main advantage over using subplots or similar is that the inset position is fixed, you can resize the figure or change the main plot area and the inset will always be in the same place relative to the main axes. This was based on this answer: specific location for inset axes, just adding the cartopy way of doing things.
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
from shapely.geometry.polygon import LinearRing
extent = [-60, -30, -40, -10]
lonmin, lonmax, latmin, latmax = extent
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
ax.set_extent(extent, crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.COASTLINE)
# inset location relative to main plot (ax) in normalized units
inset_x = 1
inset_y = 1
inset_size = 0.2
ax2 = plt.axes([0, 0, 1, 1], projection=ccrs.Orthographic(
central_latitude=(latmin + latmax) / 2,
central_longitude=(lonmin + lonmax) / 2))
ax2.set_global()
ax2.add_feature(cfeature.LAND)
ax2.add_feature(cfeature.OCEAN)
ax2.add_feature(cfeature.COASTLINE)
ip = InsetPosition(ax, [inset_x - inset_size / 2,
inset_y - inset_size / 2,
inset_size,
inset_size])
ax2.set_axes_locator(ip)
nvert = 100
lons = np.r_[np.linspace(lonmin, lonmin, nvert),
np.linspace(lonmin, lonmax, nvert),
np.linspace(lonmax, lonmax, nvert)].tolist()
lats = np.r_[np.linspace(latmin, latmax, nvert),
np.linspace(latmax, latmax, nvert),
np.linspace(latmax, latmin, nvert)].tolist()
ring = LinearRing(list(zip(lons, lats)))
ax2.add_geometries([ring], ccrs.PlateCarree(),
facecolor='none', edgecolor='red', linewidth=0.75)
I may have figured something out.
According to the answer this question. I can get the position of both axes, then reposition the 2nd axes. The code was like:
import matplotlib.pyplot as plt
from cartopy import crs as ccrs
fig, ax = plt.subplots(1, 1, subplot_kw={'projection': ccrs.PlateCarree()})
ax2 = fig.add_axes([0.8, 0.8, 0.2, 0.2], projection=ccrs.PlateCarree())
ax.set_extent([100, 120, 20, 40])
ax.coastlines()
ax2.set_global()
ax2.coastlines()
ax2.stock_img()
def reposition():
plt.draw()
p1 = ax.get_position()
p2 = ax2.get_position()
ax2.set_position([p1.x1-p2.width, p1.y1-p2.height, p2.width, p2.height])
reposition()
plt.show()
The result is just what I want.

How do I change the fontsize of the base and exponent on my colorbar?

I'd like to change the size of the base and exponent to match the fontsize of the ticks on my colorbar. How can I do this?
for i in xrange(col):
plt.plot( t, x[i], color = s_m.to_rgba(slopes[i]), linewidth = 3 )
cbar = plt.colorbar(s_m)
cbar.formatter.set_powerlimits((0, 0))
cbar.update_ticks()
cbar.ax.tick_params(labelsize=20)
First off, let's cobble together a stand-alone example to demonstrate your problem. You've changed the size of the colorbar's tick labels, but the offset label didn't update. For example, it would be nice if the text at the top of the colorbar matched the size of the tick labels:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.random((10, 10)) * 1e-6
fig, ax = plt.subplots()
im = ax.imshow(data)
cbar = fig.colorbar(im)
cbar.ax.tick_params(labelsize=20)
ax.set(xticks=[], yticks=[])
plt.show()
What you're wanting to change is referred to as the offset_text. In this case, it's the offset text of the y-axis of the colorbar. You'd want to do something similar to:
cbar.ax.yaxis.get_offset_text.set(size=20)
or
cbar.ax.yaxis.offsetText.set(size=20)
As a complete example:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.random((10, 10)) * 1e-6
fig, ax = plt.subplots()
im = ax.imshow(data)
cbar = fig.colorbar(im)
cbar.ax.tick_params(labelsize=20)
ax.set(xticks=[], yticks=[])
cbar.ax.yaxis.get_offset_text().set(size=20)
plt.show()

Categories