Matplotlib - Split graph creation into multiple functions - matplotlib

In order to create figures with some same graphs, I would like to define a function per group of graph. These should be called depending on the subfigure provided in order to have these graphs at the right location. Consequently, I would liek to split this code below into separate functions as a code like the one provided after this one.
fig = plt.figure(constrained_layout=True, figsize=(10, 8))
# create top/bottom subfigs
(subfig_t, subfig_b) = fig.subfigures(2, 1, hspace=0.05, height_ratios=[1, 3])
# put ax0 in top subfig
ax0 = subfig_t.subplots()
ax0.set_title('ax0')
subfig_t.supxlabel('xlabel0')
# create left/right subfigs nested in bottom subfig
(subfig_bl, subfig_br) = subfig_b.subfigures(1, 2, wspace=0.1, width_ratios=[3, 1])
# put ax1-ax3 in gridspec of bottom-left subfig
gs = subfig_bl.add_gridspec(nrows=1, ncols=9)
ax1 = subfig_bl.add_subplot(gs[0, :1])
ax2 = subfig_bl.add_subplot(gs[0, 1:6], sharey=ax1)
ax3 = subfig_bl.add_subplot(gs[0, 6:], sharey=ax1)
ax1.set_title('ax1')
ax2.set_title('ax2')
ax3.set_title('ax3')
ax2.get_yaxis().set_visible(False)
ax3.get_yaxis().set_visible(False)
subfig_bl.supxlabel('xlabel1-3')
# put ax4 in bottom-right subfig
ax4 = subfig_br.subplots()
ax4.set_title('ax4')
subfig_br.supxlabel('xlabel4')
Below is the code-like I would like to have, to avoid to write the same code multiple times.
fig = plt.figure(constrained_layout=True, figsize=(10, 8))
# create top/bottom subfigs
(subfig_t, subfig_b) = fig.subfigures(2, 1, hspace=0.05, height_ratios=[1, 3])
(subfig_bl, subfig_br) = subfig_b.subfigures(1, 2, wspace=0.1, width_ratios=[3, 1])
def func1(subfig_t):
# put ax0 in top subfig
ax0 = subfig_t.subplots()
ax0.set_title('ax0')
subfig_t.supxlabel('xlabel0')
return subfig_t
def func2(subfig_bl):
# put ax1-ax3 in gridspec of bottom-left subfig
gs = subfig_bl.add_gridspec(nrows=1, ncols=9)
ax1 = subfig_bl.add_subplot(gs[0, :1])
ax2 = subfig_bl.add_subplot(gs[0, 1:6], sharey=ax1)
ax3 = subfig_bl.add_subplot(gs[0, 6:], sharey=ax1)
ax1.set_title('ax1')
ax2.set_title('ax2')
ax3.set_title('ax3')
ax2.get_yaxis().set_visible(False)
ax3.get_yaxis().set_visible(False)
subfig_bl.supxlabel('xlabel1-3')
return subfig_bl
def func3(subfig_br):
# put ax4 in bottom-right subfig
ax4 = subfig_br.subplots()
ax4.set_title('ax4')
subfig_br.supxlabel('xlabel4')
return subfig_bl
def func_save(fig, OutputPath):
fig.savefig(OutputPath, dpi=300, format='png', bbox_inches='tight')
subfig_t = func1(subfig_t)
subfig_bl = func2(subfig_bl)
subfig_br = func3(subfig_br)
func_save(fig, OutputPath)

The functions are not defined as functions, few of the syntax changes and the code is good to run. Python syntax is quite different from other programming languages. It is very simple to learn, and even complex to understand the unknown.
The below code will run perfectly, hope you find it useful.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(constrained_layout=True, figsize=(10, 8))
# create top/bottom subfigs
(subfig_t, subfig_b) = fig.subfigures(2, 1, hspace=0.05, height_ratios=[1, 3])
(subfig_bl, subfig_br) = subfig_b.subfigures(1, 2, wspace=0.1, width_ratios=[3, 1])
def func1(subfig_t):
# put ax0 in top subfig
ax0 = subfig_t.subplots()
ax0.set_title('ax0')
subfig_t.supxlabel('xlabel0')
return subfig_t
def func2(subfig_bl):
# put ax1-ax3 in gridspec of bottom-left subfig
gs = subfig_bl.add_gridspec(nrows=1, ncols=9)
ax1 = subfig_bl.add_subplot(gs[0, :1])
ax2 = subfig_bl.add_subplot(gs[0, 1:6], sharey=ax1)
ax3 = subfig_bl.add_subplot(gs[0, 6:], sharey=ax1)
ax1.set_title('ax1')
ax2.set_title('ax2')
ax3.set_title('ax3')
ax2.get_yaxis().set_visible(False)
ax3.get_yaxis().set_visible(False)
subfig_bl.supxlabel('xlabel1-3')
return subfig_bl
def func3(subfig_br):
# put ax4 in bottom-right subfig
ax4 = subfig_br.subplots()
ax4.set_title('ax4')
subfig_br.supxlabel('xlabel4')
return subfig_bl
def func_save(fig, OutputPath):
fig.savefig(OutputPath, dpi=300, format='png', bbox_inches='tight')
# Enter the path for output here
OutputPath = "output.png"
subfig_t = func1(subfig_t)
subfig_bl = func2(subfig_bl)
subfig_br = func3(subfig_br)
func_save(fig, OutputPath)
Happy coding :)

Related

Adding coastlines to GOES data

I have a lot of GOES data that I'd like to plot with coastlines. I've added matplotlib axes with the proper project of each data and plotted the data. I tried to add coastlines with cartopy but they do not appear.
import metpy
import xarray as xr
import matplotlib.pyplot as plt
def get_projection(ds, variable):
dat = ds.metpy.parse_cf(variable)
return dat.metpy.cartopy_crs
aod = xr.open_dataset('GOES/AODC/2021/001/00/OR_ABI-L2-AODC-M6_G16_s20210010001176_e20210010003549_c20210010006090.nc')
albedo = xr.open_dataset('GOES/LSAC/2021/230/21/OR_ABI-L2-LSAC-M6_G16_s20212302131172_e20212302133545_c20212302135044.nc')
rainfall = xr.open_dataset('GOES/RRQPEF/2021/001/00/OR_ABI-L2-RRQPEF-M6_G16_s20210010000209_e20210010009517_c20210010010020.nc')
precipitable_water = xr.open_dataset('GOES/TPWC/2021/001/00/OR_ABI-L2-TPWC-M6_G16_s20210010001176_e20210010003549_c20210010005512.nc')
aod_proj = get_projection(aod, 'AOD')
albedo_proj = get_projection(albedo, 'LSA')
rainfall_proj = get_projection(rainfall, 'RRQPE')
pwv_proj = get_projection(precipitable_water, 'TPW')
figsize = (11, 8.5)
fig = plt.figure(figsize=figsize)
ax1 = fig.add_subplot(2, 2, 1, projection=aod_proj)
ax2 = fig.add_subplot(2, 2, 2, projection=albedo_proj)
ax3 = fig.add_subplot(2, 2, 3, projection=rainfall_proj)
ax4 = fig.add_subplot(2, 2, 4, projection=pwv_proj)
aod['AOD'].plot(vmin=0, vmax=1, ax=ax1, transform=aod_proj)
albedo['LSA'].plot(ax=ax2, transform=albedo_proj)
rainfall['RRQPE'].plot(ax=ax3, transform=rainfall_proj)
precipitable_water['TPW'].plot(ax=ax4, transform=pwv_proj)
for ax in [ax1, ax2, ax3, ax4]:
ax.coastlines(resolution='50m', color='red', linewidth=1)
fig.tight_layout()
What I get is an image with no coastlines. I'm assuming that this is a projection issue but I don't know where the problem would be.

probelm with subplots in matplotlib

I have the following code which works just fine:
plt.rcParams["figure.figsize"] = (5,5) # V1.0b
fig, axes = plt.subplots(ncols = 2, nrows = 2) # V1.0b
ax1, ax2, ax3, ax4 = axes.flatten()
plt.subplot(2, 2, 1)
ax1.plot(x1, y1)
ax1.plot(x2, y2)
(etc)
Exactly as expected, I get 2 plots in row 1, 2 plots in row 2.
Now, I want 2 rows by 3 cols and 4 plots (from exactly the same data):
plt.rcParams["figure.figsize"] = (6,4)
fig, axes = plt.subplots(ncols = 3, nrows = 2)
ax1, ax2, ax3, ax4 = axes.flatten()
plt.subplot(2, 3, 1)
ax1.plot(x1, y1)
(etc)
And I get an error from the line:
---> 12 ax1, ax2, ax3, ax4 = axes.flatten()
The error message is:
ValueError: too many values to unpack (expected 4)
Surely ax1, ax2, ax3, ax4 are the 4 values? But, evidently not; what's going wrong here?
I've found this works. As you say, no need for subplots:
figure, axis = plt.subplots(3, 3)
axis[0, 0]
axis[0, 0].set_title("NGC0628")
axis[0, 0].plot(x0,y0)
axis[0, 1]
axis[0, 0].plot(x1,y1)
axis[0, 2]
axis[0, 0].plot(x2,y2)
(etc)
BTW I need control over each plot, i.e. as in
axis[0, 0].set_title("NGC0628")
Thanks for the steer

Changing the Matplotlib GridSpec properties after generating the subplots

Suppose something comes up in my plot that mandates that I change the height ratio between two subplots that I've generated within my plot. I've tried changing GridSpec's height ratio to no avail.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
fig = plt.figure()
gs = GridSpec(2, 1, height_ratios=[2, 1])
ax1 = fig.add_subplot(gs[0])
ax1 = fig.axes[0]
ax2 = fig.add_subplot(gs[1])
ax2 = fig.axes[1]
ax1.plot([0, 1], [0, 1])
ax2.plot([0, 1], [1, 0])
gs.height_ratios = [2, 5]
The last line has no effect on the plot ratio.
In my actual code, it is not feasible without major reworking to set the height_ratios to 2:5 ahead of time.
How do I get this to update like I want?
The axes of relevant subplots can be manipulated and adjusted to get new height ratios.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
fig = plt.figure()
gs = GridSpec(2, 1, height_ratios=[2, 1]) #nrows, ncols
ax1 = fig.add_subplot(gs[0])
ax1 = fig.axes[0]
ax2 = fig.add_subplot(gs[1])
ax2 = fig.axes[1]
ax1.plot([0, 1], [0, 1])
ax2.plot([0, 1], [1, 0])
# new height ratio: 2:5 is required for the 2 subplots
rw, rh = 2, 5
# get dimensions of the 2 axes
box1 = ax1.get_position()
box2 = ax2.get_position()
# current dimensions
w1,h1 = box1.x1-box1.x0, box1.y1-box1.y0
w2,h2 = box2.x1-box2.x0, box2.y1-box2.y0
top1 = box1.y0+h1
#top2 = box2.y0+h2
full_h = h1+h2 #total height
# compute new heights for each axes
new_h1 = full_h*rw/(rw + rh)
new_h2 = full_h*rh/(rw + rh)
#btm1,btm2 = box1.y0, box2.y0
new_bottom1 = top1-new_h1
# finally, set new location/dimensions of the axes
ax1.set_position([box1.x0, new_bottom1, w1, new_h1])
ax2.set_position([box2.x0, box2.y0, w2, new_h2])
plt.show()
The output for ratio: (2, 5):
The output for (2, 10):

How can I increase Horizontal Space (hspace) between two specific matplotlib subplots?

f = plt.figure(figsize=(12,10))
ax1 = f.add_subplot(411)
ax2 = f.add_subplot(422)
ax3 = f.add_subplot(423)
ax4 = f.add_subplot(424)
ax5 = f.add_subplot(425)
ax6 = f.add_subplot(426)
ax7 = f.add_subplot(427)
ax8 = f.add_subplot(428)
I want to increase space between two rows: ax1 and ax2-ax3. Other spaces should remain the same. Using "f.subplots_adjust(hspace = 0.2, wspace= 0.25)" adjusts the spacing for all subplots. What can I do to increase hspace for the top-most subplot only?
import matplotlib.pyplot as plt
fig, axs = plt.subplot_mosaic([['top', 'top'],['left1', 'right1'], ['left2', 'right2']],
constrained_layout=True)
axs['top'].set_xlabel('Xlabel\n\n')
plt.show()
This will make all the y-axes the same size. If that is not important to you, then #r-beginners answer is helpful. Note that you need-not use subplot mosaic, though it is a useful new feature.
If you are not worried about the axes sizes matching, then a slightly better way than proposed above is to use the new subfigure functionality:
import matplotlib.pyplot as plt
fig = plt.figure(constrained_layout=True)
subfigs = fig.subfigures(2, 1, height_ratios=[1, 2], hspace=0.15)
# top
axtop = subfigs[0].subplots()
# 2x2 grid
axs = subfigs[1].subplots(2, 2)
plt.show()
Based on the gridspec sample in the official reference, I customized it using this example answer.The point is to use gridspec for the separate graphs you want to configure.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
def format_axes(fig):
for i, ax in enumerate(fig.axes):
ax.text(0.5, 0.5, "ax%d" % (i+1), va="center", ha="center")
ax.tick_params(labelbottom=False, labelleft=False)
fig = plt.figure()
gs_top = GridSpec(3, 3, top=0.95)
gs_base = GridSpec(3, 3)
ax1 = fig.add_subplot(gs_top[0, :])
# identical to ax1 = plt.subplot(gs.new_subplotspec((0, 0), colspan=3))
ax2 = fig.add_subplot(gs_base[1, :-1])
ax3 = fig.add_subplot(gs_base[1:, -1])
ax4 = fig.add_subplot(gs_base[-1, 0])
ax5 = fig.add_subplot(gs_base[-1, -2])
# fig.suptitle("GridSpec")
format_axes(fig)
plt.show()

multiple matplotlib gridspec's in single figure BUT each one with their own common title

I know I can use update to adjust the parameters of a GridSpec instance in a matplotlib figure, allowing to arrange multiple gridspec's in a single figure. Much as in this example taken from the matplotlib doc
gs1 = gridspec.GridSpec(3, 3)
gs1.update(left=0.05, right=0.48, wspace=0.05)
ax1 = plt.subplot(gs1[:-1, :])
ax2 = plt.subplot(gs1[-1, :-1])
ax3 = plt.subplot(gs1[-1, -1])
gs2 = gridspec.GridSpec(3, 3)
gs2.update(left=0.55, right=0.98, hspace=0.05)
ax4 = plt.subplot(gs2[:, :-1])
ax5 = plt.subplot(gs2[:-1, -1])
ax6 = plt.subplot(gs2[-1, -1])
But how can I give both gs1 and gs2 their own common title? Using suptitle I only get a common title for the whole figure at once.
I can think of four ways, all quite ugly. I do not know if there are any automatic way of setting such things.
The four ugly ways are:
1) Set the title to the "top" axis-object in each group with ax.set_title() (in your case ax1 and ax4). It works great on the left group, but horrible for the right group...
2) Set one title with fig.suptitle, but make a lot of spaces inside the title, and use horizontalalignment='center'.
3) Set a text-object manually for each title... (not in the example below, but just look at matplotlib.text)
4) Create ghost axes, hide everything on them and just use them to set their title...
Below is some example code
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
gs1 = gridspec.GridSpec(3, 3)
gs1.update(left=0.05, right=0.48, wspace=0.05)
ax1 = fig.add_subplot(gs1[:-1, :])
ax2 = fig.add_subplot(gs1[-1, :-1])
ax3 = fig.add_subplot(gs1[-1, -1])
ax1.set_title('Left group title') # Alternative 1)
gs2 = gridspec.GridSpec(3, 3)
gs2.update(left=0.55, right=0.98, hspace=0.05)
ax4 = fig.add_subplot(gs2[:, :-1])
ax5 = fig.add_subplot(gs2[:-1, -1])
ax6 = fig.add_subplot(gs2[-1, -1])
ax4.set_title('Right group title') # Alternative 1)
# Alternative 2. Note the many white-spaces
fig.suptitle('figure title left figure title right', horizontalalignment='center')
# Alternative 4)
rect_left = 0, 0, 0.5, 0.8 # lower, left, width, height (I use a lower height than 1.0, to place the title more visible)
rect_right = 0.5, 0, 0.5, 0.8
ax_left = fig.add_axes(rect_left)
ax_right = fig.add_axes(rect_right)
ax_left.set_xticks([])
ax_left.set_yticks([])
ax_left.spines['right'].set_visible(False)
ax_left.spines['top'].set_visible(False)
ax_left.spines['bottom'].set_visible(False)
ax_left.spines['left'].set_visible(False)
ax_left.set_axis_bgcolor('none')
ax_right.set_xticks([])
ax_right.set_yticks([])
ax_right.spines['right'].set_visible(False)
ax_right.spines['top'].set_visible(False)
ax_right.spines['bottom'].set_visible(False)
ax_right.spines['left'].set_visible(False)
ax_right.set_axis_bgcolor('none')
ax_left.set_title('Ghost left title')
ax_right.set_title('Ghost right title')
plt.show()
Way too late, but just found this thread when searching for the exact same thing, so just leaving this for anyone stumbling across it.
I think #pathoren's Alternative 4) is the way to go, but you can reuse the existing gridspec to create your ghost axis such that it exactly matches the existing ones:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
gs1 = gridspec.GridSpec(3, 3)
gs1.update(left=0.05, right=0.48, wspace=0.05)
ax1 = fig.add_subplot(gs1[:-1, :])
ax2 = fig.add_subplot(gs1[-1, :-1])
ax3 = fig.add_subplot(gs1[-1, -1])
gs2 = gridspec.GridSpec(3, 3)
gs2.update(left=0.55, right=0.98, hspace=0.05)
ax4 = fig.add_subplot(gs2[:, :-1])
ax5 = fig.add_subplot(gs2[:-1, -1])
ax6 = fig.add_subplot(gs2[-1, -1])
# Add ghost axes and titles on gs1 and gs2
ax_left = fig.add_subplot(gs1[:])
ax_left.axis('off')
ax_left.set_title('Left title')
ax_right = fig.add_subplot(gs2[:])
ax_right.axis('off')
ax_right.set_title('Right title')
plt.show()
Resulting layout: