Matplotlib with Cartopy and gridspec - python

I would like to create a plot with a Cartopy plot on the left-hand side and two stacked Matplotlib plots on the right-hand side. If I'd only use Matplotlib plots, the code would be as follows.
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
gs = gridspec.GridSpec(2, 2)
# LEFT
ax = fig.add_subplot(gs[:, 0])
ax.plot(np.arange(0, 1000, 100))
# RIGHT TOP
ax = fig.add_subplot(gs[0, 1])
ax.plot(np.arange(0, 1000, 100))
# RIGHT BOTTOM
ax = fig.add_subplot(gs[1, 1])
ax.plot(np.arange(0, 1000, 100))
plt.show()
... so far so good.
However, if I add a Cartopy plot, I don't manage to make it stick to the axis on the left-hand side. I suppose there's a problem with how I use ax = plt.axes().
import cartopy.crs as ccrs
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
gs = gridspec.GridSpec(2, 2)
# LEFT
ax = fig.add_subplot(gs[:, 0])
ax = plt.axes(
projection = ccrs.Orthographic(
central_longitude=0,
central_latitude=0
)
)
ax.stock_img()
# RIGHT TOP
ax = fig.add_subplot(gs[0, 1])
ax.plot(np.arange(0, 1000, 100))
# RIGHT BOTTOM
ax = fig.add_subplot(gs[1, 1])
ax.plot(np.arange(0, 1000, 100))
plt.show()
How can I make the Cartopy plot stick to the axis of the subplot on the left-hand side?

That happens because after creating the left-pane, you created a new axis for cartopy covering the entire figure. Instead, you need to pass projection inside fig.add_subplot, like this:
import cartopy.crs as ccrs
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
gs = gridspec.GridSpec(2, 2)
# LEFT
ax = fig.add_subplot(gs[:, 0], projection = ccrs.Orthographic(
central_longitude=0,
central_latitude=0
))
ax.stock_img()
# RIGHT TOP
ax = fig.add_subplot(gs[0, 1])
ax.plot(np.arange(0, 1000, 100))
# RIGHT BOTTOM
ax = fig.add_subplot(gs[1, 1])
ax.plot(np.arange(0, 1000, 100))
plt.show()

Related

Animation issue in Python

I want the green rectangle to not disappear as it moves from one value to another in matrix b. For example, the rectangle is around 0.24671953. Then the rectangle stays on this value. Then another rectangle appears onto the next value which is 0.25959473. Then another rectangle appears on 0.41092171, with the previous two rectangles not disappearing.
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import seaborn as sns
import numpy as np
from celluloid import Camera
a = np.array([[0.24671953, 0.25959473, 0.85494718],
[0.60553861, 0.76276659, 0.41092171],
[0.37356358, 0.69378785, 0.46988614]])
b = np.array([[0.24671953,0.25959473],
[0.41092171,0.46988614],
[0.37356358,0.60553861]])
annot=True
fig, ax1 = plt.subplots(1)
camera = Camera(fig)
sns.set_style('white')
ax1 = sns.heatmap(a, linewidth=0.5,ax=ax1,annot=annot)
for bb in b.flatten():
ax1.add_patch(plt.Rectangle((np.where(a == bb)[1][0],
np.where(a == bb)[0][0]), 1, 1, fc='none', ec='green', lw=5, clip_on=False))
camera.snap()
animation = camera.animate(interval=800)
animation.save('animation2.gif')
plt.show()
It looks like celluloid clears the existing plot at each snap. You can recreate the plot from scratch at each step. The rectangles can be stored in a list.
To avoid that new colorbar positions are set at each step, you can use sns.heatmap's cbar_ax= parameter to always use the same colorbar ax:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from celluloid import Camera
a = np.array([[0.24671953, 0.25959473, 0.85494718],
[0.60553861, 0.76276659, 0.41092171],
[0.37356358, 0.69378785, 0.46988614]])
b = np.array([[0.24671953, 0.25959473],
[0.41092171, 0.46988614],
[0.37356358, 0.60553861]])
fig, (ax1, cbar_ax) = plt.subplots(ncols=2, gridspec_kw={'width_ratios': [20, 1]})
camera = Camera(fig)
sns.set_style('white')
rectangles = []
for bb in b.flatten():
sns.heatmap(a, linewidth=0.5, ax=ax1, annot=True, cbar_ax=cbar_ax)
rectangles.append(plt.Rectangle((np.where(a == bb)[1][0], np.where(a == bb)[0][0]), 1, 1,
fc='none', ec='green', lw=5, clip_on=False))
for rect in rectangles:
ax1.add_patch(rect)
camera.snap()
animation = camera.animate(interval=800)
animation.save('animation2.gif')
plt.show()
An alternative could be to directly use matplotlib's animation framework. Then, the code could look like:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import seaborn as sns
import numpy as np
a = np.array([[0.24671953, 0.25959473, 0.85494718],
[0.60553861, 0.76276659, 0.41092171],
[0.37356358, 0.69378785, 0.46988614]])
b = np.array([[0.24671953, 0.25959473],
[0.41092171, 0.46988614],
[0.37356358, 0.60553861]])
fig, ax1 = plt.subplots()
sns.set_style('white')
sns.heatmap(a, linewidth=0.5, ax=ax1, annot=True)
def animate(i):
bb = b.flatten()[i]
patch = ax1.add_patch(plt.Rectangle((np.where(a == bb)[1][0], np.where(a == bb)[0][0]), 1, 1,
fc='none', ec='green', lw=5, clip_on=False))
return patch,
animation = FuncAnimation(fig, animate, frames=range(0, b.size), interval=800, repeat=False)
animation.save('animation2.gif')
plt.show()

How to display the grid when multiple matplotlib charts are created at the same time?

I would like to use the grid in multiple matplotlib figures, but if I just use plt.grid() the grid would only show up in one of the charts.
How can I change the code below, so that the grid shows up in both figures, please?
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(19680801)
N_points = 100000
dist1 = rng.standard_normal(N_points)
fig = plt.figure()
axis = fig.add_subplot(1,1,1)
fig1 = plt.figure()
ax = fig1.add_subplot(1,1,1)
axis.hist(dist1)
ax.hist(dist1)
plt.grid()
plt.show()
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(19680801)
N_points = 100000
dist1 = rng.standard_normal(N_points)
fig = plt.figure()
axis = fig.add_subplot(1,1,1)
axis.grid()
fig1 = plt.figure()
ax = fig1.add_subplot(1,1,1)
ax.grid()
axis.hist(dist1)
ax.hist(dist1)
# plt.grid()
plt.show()

Matplotlib: how to remove spacing between a group of subplots

I have a series of pyplot subplots that I've created using a gridspec. They all have an hspace between them, which is fine, except that I would like to keep three of them without any space. Is there a way to do this? Currently, they look like this:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
grid_spec = gridspec.GridSpec(nrows=10, ncols=10)
grid_spec.update(hspace=1.5)
ax1 = plt.subplot(grid_spec[0:4, :])
ax2 = plt.subplot(grid_spec[4:7, :], sharex=ax1)
# I would like to group the next 3 together
# so that they are stacked top to bottom and side by side
ax3 = plt.subplot(grid_spec[7:8, :5])
ax4 = plt.subplot(grid_spec[8:, :5], sharex=ax3)
ax5 = plt.subplot(grid_spec[8:, 5:6], sharey=ax4)
plt.show()
I would like them to be arranged like this so I can plot the following 2-D KDE diagram and have the relevant 1-D diagrams above and to the right (roughly displaying this sort of data crudely drawn in paint):
I appreciate any help with this one. Can't seem to find documentation on this sort of thing. Thanks!
You can use mpl_toolkits.axes_grid1.make_axes_locatable to subdivide the area of a subplot of a 3 x 2 grid.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig = plt.figure()
gs = fig.add_gridspec(nrows=3, ncols=2, hspace=.5,
height_ratios=[4, 3, 3], width_ratios=[7, 4])
ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, :], sharex=ax1)
ax3 = fig.add_subplot(gs[2, 0])
div = make_axes_locatable(ax3)
ax4 = div.append_axes("top", "40%", pad=0.2, sharex=ax3)
ax5 = div.append_axes("right", "25%", pad=0.2, sharey=ax3)
ax4.tick_params(labelbottom=False)
ax5.tick_params(labelleft=False)
plt.show()
Also, you can create a subgridspec, like
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure()
gs = gridspec.GridSpec(nrows=3, ncols=2, hspace=.5,
height_ratios=[4, 3, 3], width_ratios=[7, 4])
ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, :], sharex=ax1)
sub_gs = gridspec.GridSpecFromSubplotSpec(2,2, subplot_spec=gs[2,0], hspace=0.3, wspace=0.1,
height_ratios=[1,3], width_ratios=[3,1])
ax3 = fig.add_subplot(sub_gs[1,0])
ax4 = fig.add_subplot(sub_gs[0,0], sharex=ax3)
ax5 = fig.add_subplot(sub_gs[1,1], sharey=ax3)
ax4.tick_params(labelbottom=False)
ax5.tick_params(labelleft=False)
plt.show()
In both cases you will probably want to fine tune the parameters a bit. In general, the matplotlib gridspec tutorial gives a nice overview with many examples on this matter.

How to set discrete colorbar ticks in mpl_toolkits.axes_grid1.ImageGrid?

I want to set discrete colorbar in ImageGrid.
ImageGrid
Here's an example:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
import numpy as np
import matplotlib
lon,lat = np.meshgrid(np.arange(-180, 180, 10), np.arange(-85, 90, 10))
data = np.sort(np.random.rand(18, 36),axis=1)
fig = plt.figure()
grid = ImageGrid(fig, 111,
nrows_ncols=(2, 1),
axes_pad=(0.35, 0.35),
label_mode="1",
share_all=True,
cbar_location="right",
cbar_mode="each",
cbar_size="5%",
cbar_pad="6%",
)
# Settings
bounds = [0,0.01,0.04,0.07,0.1,0.13,0.16,0.2,0.25,0.35,0.45,0.6,0.9]
colors = ['#390231','#7F1CAB','#0047FD','#0072FE','#019EFF','#00C4FF','#01EDFF',\
'#00FFFB','#00FFC8','#29F905','#FBDD03','#FA0F00']
# Original colorbar
p = grid[0].pcolormesh(lon,lat,data, vmin=0, vmax=0.9, cmap='jet')
cb = grid.cbar_axes[0].colorbar(p)
# Defined colorbar
cmap = matplotlib.colors.ListedColormap(colors)
norm = matplotlib.colors.BoundaryNorm(bounds, cmap.N)
p = grid[1].pcolormesh(lon,lat,data, cmap=cmap, norm=norm)
cb = grid.cbar_axes[1].colorbar(p, ticks=bounds)
grid[0].set_title('jet')
grid[1].set_title('Defined')
plt.show()
This is the result:
As you can see, the location of ticks are wrong.
If ticks are at boundaries of each color block, the second figure will look correct.
Subplots
Then, I tested subplots. It works fine!
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
lon,lat = np.meshgrid(np.arange(-180, 180, 10), np.arange(-85, 90, 10))
data = np.sort(np.random.rand(18, 36),axis=1)
f, (ax1, ax2) = plt.subplots(1, 2,sharey=True)
# Settings
bounds = [0,0.01,0.04,0.07,0.1,0.13,0.16,0.2,0.25,0.35,0.45,0.6,0.9]
colors = ['#390231','#7F1CAB','#0047FD','#0072FE','#019EFF','#00C4FF','#01EDFF',\
'#00FFFB','#00FFC8','#29F905','#FBDD03','#FA0F00']
# Defined colorbar
cmap = matplotlib.colors.ListedColormap(colors)
norm = matplotlib.colors.BoundaryNorm(bounds, cmap.N)
# Jet
p = ax1.pcolormesh(lon,lat,data, vmin=0, vmax=0.9, cmap='jet')
f.colorbar(p,ax=ax1)
ax1.set_title('jet')
# Defined
p = ax2.pcolormesh(lon,lat,data, cmap=cmap, norm=norm)
f.colorbar(p,ax=ax2,ticks=bounds)
ax2.set_title('defined')
plt.show()
This is the result:
Single
I tested my script in single figure. It works fine!
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
lon,lat = np.meshgrid(np.arange(-180, 180, 10), np.arange(-85, 90, 10))
data = np.sort(np.random.rand(18, 36),axis=1)
fig = plt.figure()
# Settings
bounds = [0,0.01,0.04,0.07,0.1,0.13,0.16,0.2,0.25,0.35,0.45,0.6,0.9]
colors = ['#390231','#7F1CAB','#0047FD','#0072FE','#019EFF','#00C4FF','#01EDFF',\
'#00FFFB','#00FFC8','#29F905','#FBDD03','#FA0F00']
# Defined colorbar
cmap = matplotlib.colors.ListedColormap(colors)
norm = matplotlib.colors.BoundaryNorm(bounds, cmap.N)
# Jet
plt.pcolormesh(lon,lat,data, vmin=0, vmax=0.9, cmap='jet')
plt.colorbar()
plt.show()
# Defined
p = plt.pcolormesh(lon,lat,data, cmap=cmap, norm=norm)
plt.colorbar(p, ticks=bounds)
plt.title('Single fig')
plt.show()
This is the result of single figure of jet and defined:
A workaround would be to set the labels manually.
ticks=np.linspace(bounds[0],bounds[-1], len(bounds))
cb = grid.cbar_axes[1].colorbar(p, ticks=ticks)
cb.ax.set_yticklabels(bounds)

How to increase the size of a single subfigure with pyplot/gridspec?

I'm trying to plot 23 graphs in a 6x4 grid, with one figure taking up twice the width of the other figures. I'm using gridspec and my current code is:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
x = np.arange(0, 7, 0.01)
fig = plt.figure(figsize=(6, 4))
gs = gridspec.GridSpec(nrows=6, ncols=4)
for n in range(22):
ax = fig.add_subplot(gs[n])
ax.plot(x, np.sin(0.2*n*x))
corrax = fig.add_subplot(gs[22])
fig.tight_layout()
plt.show()
This produces the following:
I want to increase the width of the rightmost plot in the bottom row so it takes up the remaining space in that row. Is there a way to accomplish this?
You can use slices to select several positions from the gridspec, e.g. gs[22:24].
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
x = np.arange(0, 7, 0.01)
fig = plt.figure(figsize=(6, 4))
gs = gridspec.GridSpec(nrows=6, ncols=4)
for n in range(22):
ax = fig.add_subplot(gs[n])
ax.plot(x, np.sin(0.2*n*x))
corrax = fig.add_subplot(gs[22:24])
corrax.plot(x,np.sin(0.2*22*x), color="crimson", lw=3)
fig.tight_layout()
plt.show()
You can also slice the gridspec two-dimensionally. E.g. to create a 3x3 grid and make the plot in the lower right corner span two columns and two rows, you could slice like gs[1:,1:].
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
x = np.arange(0, 7, 0.01)
fig = plt.figure(figsize=(6, 4))
gs = gridspec.GridSpec(nrows=3, ncols=3)
for n in range(3):
ax = fig.add_subplot(gs[0,n])
ax.plot(x, np.sin(0.2*n*x))
if n !=0:
ax = fig.add_subplot(gs[n,0])
ax.plot(x, np.sin(0.2*n*x))
corrax = fig.add_subplot(gs[1:,1:])
corrax.plot(x,np.sin(0.2*22*x), color="crimson", lw=3)
fig.tight_layout()
plt.show()
#corrax = fig.add_subplot(gs[5,2:])
corrax = fig.add_subplot(6,4,(23,24))
both shold work.
see examples

Categories

Resources