Pyplot subfigures and gridspec - problems with constrained_layout and suptitle - matplotlib

I am trying to create a figure in pyplot with specific gridspec, but I got into a dead end.
When using subfigures it is needed to use constrained_layout = True option in the figure definition, but it disables the hspace = 0 option in the GridSpec definition.
An example looks like this:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
fig = plt.figure(
figsize = (16,9),
constrained_layout = True
)
fig.suptitle("fig suptitle")
subfigs = fig.subfigures(1,3)
for sf in subfigs:
sf.suptitle("subfig suptitle")
gs = GridSpec(6,1,sf,
hspace = 0
)
ax1 = sf.add_subplot(gs[0:2,0])
ax1.scatter([1,1,5,2,7,5], [1,5,8,2,6,8])
ax1.set_title("ax title")
for i in range(3,6):
ax = sf.add_subplot(gs[i,0],
sharex = ax1
)
if i == 3:
ax.set_title("ax title")
ax.plot([6,2,5,5,6,8,7])
# ax.set_title("ax title")
When I comment the constrained_layout = True line the plots are in the right possitions, but fig suptitle disappears.
Is there any workaround that keeps the fig suptitle shown and leaves hspace = 0?

Related

Matplotlib clearing the figure/axis for new plot

am trying to figure out how to clear the axis in readiness for new plotting, I have tried ax.clf(), fig.clf() but nothing is happening. where am I not doing well? at the moment am not getting any errors and am using Matplotlib vers. 3.4.3.
from tkinter import *
import matplotlib.pyplot as plt
import numpy as np
import time
import datetime
import mysql.connector
import matplotlib.dates as mdates
my_connect = mysql.connector.connect(host="localhost", user="Kennedy", passwd="Kennerdol05071994", database="ecg_db", auth_plugin="mysql_native_password")
mycursor = my_connect.cursor()
voltage_container = []
time_container = []
def analyze_voltage_time():
global ax, fig
pat_id = 1
query = "SELECT voltage, time FROM ecg_data_tbl where patient_id = " +str(pat_id)
mycursor.execute(query)
result = mycursor .fetchall()
voltage, time = list(zip(*result))
for volts in voltage:
voltage_container.append(volts)
for tim in time:
time_container.append(str(tim))
fig = plt.figure(1, figsize = (15, 6), dpi = 80, constrained_layout = True)
ax = fig.add_subplot()
ax.plot(time_container, voltage_container)
for label in ax.get_xticklabels():
label.set_rotation(40)
label.set_horizontalalignment('right')
ax.set_title("Electrocadiogram")
ax.set_xlabel("Time(hh:mm:ss)")
ax.set_ylabel("Voltage(mV)")
ax.grid(b=True, which='major', color='#666666', linestyle='-')
ax.minorticks_on()
ax.grid(b=True, which='minor', color='#666666', linestyle='-', alpha=0.2)
plt.show()
def clear_():
ax.cla()
fig.clf()
# =================================MAIN GUI WINDOW======================================
analysis_window = Tk()
analysis_window.configure(background='light blue')
analysis_window.iconbitmap('lardmon_icon.ico')
analysis_window.title("ECG-LArdmon - ANALYZER")
analysis_window.geometry('400x200')
analysis_window.resizable(width=False, height=False)
# ===========================BUTTONS===================================
analyse_btn = Button(analysis_window, text='analyze', width = 20, command=analyze_voltage_time)
analyse_btn.pack()
clear_btn = Button(analysis_window, text= 'clear', width = 20, command=clear_)
clear_btn.pack()
analysis_window.mainloop()

How do I animate a circle to move horizontally?

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
xvalues = np.arange(4000000, 6000000+1000, 1000).tolist()
yvalues = [5000000]*2001
Acc_11 = xvalues
Acc_12 = yvalues
fig = plt.figure(figsize = (5,5))
axes = fig.add_subplot(111)
axes.set_xlim((0, 10000000))
axes.set_ylim((0, 10000000))
point, = plt.Circle((4000000, 5000000), 60000, color = "black")
def ani(coords):
point.set_data([coords[0]],[coords[1]])
return point
def frames():
for acc_11_pos, acc_12_pos in zip(Acc_11, Acc_12):
yield acc_11_pos, acc_12_pos
ani = FuncAnimation(fig, ani, frames=frames, interval=10)
plt.show()
Im getting TypeError: 'Circle' object is not iterable. What I need to do? The size of a circle must be changable and related to axes, so matplotlib circle is the only option (I guess).
Here's a possible solution (assuming you are running in a jupyter notebook cell):
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
xvalues = np.arange(4000000, 6000000+1000, 1000).tolist()
yvalues = [5000000]*2001
Acc_11 = xvalues
Acc_12 = yvalues
fig = plt.figure(figsize = (5,5))
axes = fig.add_subplot(111)
axes.set_xlim((0, 10000000))
axes.set_ylim((0, 10000000))
point = plt.Circle((4000000, 5000000), 60000, color = "black")
def init():
point.center = (5, 5)
axes.add_patch(point)
return point,
def ani(i):
point.center = (Acc_11[i],Acc_12[i])
return point
anim = FuncAnimation(fig,
ani,
init_func=init,
frames=200, #len(Acc_11),
interval=10)
HTML(anim.to_html5_video())
You may want to change frames=200 to frames=len(Acc_11) but it will take a while to run.

Combine two heatmaps (different sizes), maintaing same cell size, the same color bar and the same x-axis (GridSpec),

I am trying to combine two heatmaps of different row numbers. I want to keep the same cell size for both, and that they have the same x-axis and the same color bar.
Here is what I tried so far.
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.gridspec import GridSpec
fig = plt.figure()
gs=GridSpec(16,18)
ax1 = fig.add_subplot(gs[0:6,:])
ax2 = fig.add_subplot(gs[7:17,:])
sns.heatmap(site, cmap="inferno", ax=ax1)
sns.heatmap(country, cmap="inferno", ax=ax2)
Here is the output:
Thank you very much.
You can play with the height_ratios of GridSpec:
import matplotlib.gridspec as gs
site = np.random.random(size=(2,20))
country = np.random.random(size=(20,20))
fig = plt.figure()
N_rows_site, _ = site.shape
N_rows_country, _ = country.shape
grid=gs.GridSpec(2,2, height_ratios=[N_rows_site,N_rows_country], width_ratios=[50,1])
ax1 = fig.add_subplot(grid[0,0])
ax2 = fig.add_subplot(grid[1,0], sharex=ax1)
cax = fig.add_subplot(grid[:,1])
sns.heatmap(site, cmap="inferno", ax=ax1, cbar_ax=cax)
sns.heatmap(country, cmap="inferno", ax=ax2, cbar_ax=cax)
plt.setp(ax1.get_xticklabels(), visible=False)
with a different number of lines:
site = np.random.random(size=(10,20))
country = np.random.random(size=(20,20))

Margin Boxplots Matplotlib

I would like to manually add a margin to the boxplots generated by the following code. At present the boxplots are too much in the corners (ends). In general there will be many boxplots (unlike this sample code), which I'd like equally spaced (like in the code), but I'd like to have a margin on the sides.
I am using matplotlib version 1.3.1
import matplotlib.pyplot as plt
statistic_dict = {0.40000000000000002: [0.36003616645322273, 0.40526649416305677, 0.46522159350924536], 0.20000000000000001: [0.11932912803730165, 0.23235825966896217, 0.12380728472472625]}
def draw_boxplot(y_values, x_values, edge_color, fill_color):
bp = plt.boxplot(y_values, patch_artist=True, positions=x_values, widths=(0.05,0.05))
for element in ['boxes', 'whiskers', 'fliers', 'medians', 'caps']:
plt.setp(bp[element], color=edge_color)
plt.xlabel("x label ")
plt.ylabel("y label ")
plt.title("Title")
for patch in bp['boxes']:
patch.set(facecolor=fill_color)
y_values = statistic_dict.values()
x_values = statistic_dict.keys()
draw_boxplot(y_values, x_values, "skyblue", "white")
plt.gca().autoscale()
plt.savefig('fileName.png', bbox_inches='tight')
plt.close()
The following is a hacky workaround in case ax.margins() is not working as expected.
import numpy as np
import matplotlib.pyplot as plt
statistic_dict = {0.40: [0.36, 0.40, 0.46],
0.20: [0.11, 0.23, 0.12],
0.70: [0.19, 0.23, 0.12]}
def draw_boxplot(y_values, x_values, edge_color, fill_color):
bp = plt.boxplot(y_values, patch_artist=True, positions=x_values)
for element in ['boxes', 'whiskers', 'fliers', 'medians', 'caps']:
plt.setp(bp[element], color=edge_color)
plt.xlabel("x label ")
plt.ylabel("y label ")
plt.title("Title")
for patch in bp['boxes']:
patch.set(facecolor=fill_color)
v = np.array([box.get_path().vertices for box in bp['boxes']])
margin=0.2
xmin = v[:,:5,0].min() - (max(x_values)-min(x_values))*margin
xmax = v[:,:5,0].max() + (max(x_values)-min(x_values))*margin
plt.xlim(xmin, xmax)
y_values = statistic_dict.values()
x_values = statistic_dict.keys()
draw_boxplot(y_values, x_values, "skyblue", "white")
plt.show()

Embedding small plots inside subplots in matplotlib

If you want to insert a small plot inside a bigger one you can use Axes, like here.
The problem is that I don't know how to do the same inside a subplot.
I have several subplots and I would like to plot a small plot inside each subplot.
The example code would be something like this:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
for i in range(4):
ax = fig.add_subplot(2,2,i)
ax.plot(np.arange(11),np.arange(11),'b')
#b = ax.axes([0.7,0.7,0.2,0.2])
#it gives an error, AxesSubplot is not callable
#b = plt.axes([0.7,0.7,0.2,0.2])
#plt.plot(np.arange(3),np.arange(3)+11,'g')
#it plots the small plot in the selected position of the whole figure, not inside the subplot
Any ideas?
I wrote a function very similar to plt.axes. You could use it for plotting yours sub-subplots. There is an example...
import matplotlib.pyplot as plt
import numpy as np
#def add_subplot_axes(ax,rect,facecolor='w'): # matplotlib 2.0+
def add_subplot_axes(ax,rect,axisbg='w'):
fig = plt.gcf()
box = ax.get_position()
width = box.width
height = box.height
inax_position = ax.transAxes.transform(rect[0:2])
transFigure = fig.transFigure.inverted()
infig_position = transFigure.transform(inax_position)
x = infig_position[0]
y = infig_position[1]
width *= rect[2]
height *= rect[3] # <= Typo was here
#subax = fig.add_axes([x,y,width,height],facecolor=facecolor) # matplotlib 2.0+
subax = fig.add_axes([x,y,width,height],axisbg=axisbg)
x_labelsize = subax.get_xticklabels()[0].get_size()
y_labelsize = subax.get_yticklabels()[0].get_size()
x_labelsize *= rect[2]**0.5
y_labelsize *= rect[3]**0.5
subax.xaxis.set_tick_params(labelsize=x_labelsize)
subax.yaxis.set_tick_params(labelsize=y_labelsize)
return subax
def example1():
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111)
rect = [0.2,0.2,0.7,0.7]
ax1 = add_subplot_axes(ax,rect)
ax2 = add_subplot_axes(ax1,rect)
ax3 = add_subplot_axes(ax2,rect)
plt.show()
def example2():
fig = plt.figure(figsize=(10,10))
axes = []
subpos = [0.2,0.6,0.3,0.3]
x = np.linspace(-np.pi,np.pi)
for i in range(4):
axes.append(fig.add_subplot(2,2,i))
for axis in axes:
axis.set_xlim(-np.pi,np.pi)
axis.set_ylim(-1,3)
axis.plot(x,np.sin(x))
subax1 = add_subplot_axes(axis,subpos)
subax2 = add_subplot_axes(subax1,subpos)
subax1.plot(x,np.sin(x))
subax2.plot(x,np.sin(x))
if __name__ == '__main__':
example2()
plt.show()
You can now do this with matplotlibs inset_axes method (see docs):
from mpl_toolkits.axes_grid.inset_locator import inset_axes
inset_axes = inset_axes(parent_axes,
width="30%", # width = 30% of parent_bbox
height=1., # height : 1 inch
loc=3)
Update: As Kuti pointed out, for matplotlib version 2.1 or above, you should change the import statement to:
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
There is now also a full example showing all different options available.
From matplotlib 3.0 on, you can use matplotlib.axes.Axes.inset_axes:
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2,2)
for ax in axes.flat:
ax.plot(np.arange(11),np.arange(11))
ins = ax.inset_axes([0.7,0.7,0.2,0.2])
plt.show()
The difference to mpl_toolkits.axes_grid.inset_locator.inset_axes mentionned in #jrieke's answer is that this is a lot easier to use (no extra imports etc.), but has the drawback of being slightly less flexible (no argument for padding or corner locations).
source: https://matplotlib.org/examples/pylab_examples/axes_demo.html
from mpl_toolkits.axes_grid.inset_locator import inset_axes
import matplotlib.pyplot as plt
import numpy as np
# create some data to use for the plot
dt = 0.001
t = np.arange(0.0, 10.0, dt)
r = np.exp(-t[:1000]/0.05) # impulse response
x = np.random.randn(len(t))
s = np.convolve(x, r)[:len(x)]*dt # colored noise
fig = plt.figure(figsize=(9, 4),facecolor='white')
ax = fig.add_subplot(121)
# the main axes is subplot(111) by default
plt.plot(t, s)
plt.axis([0, 1, 1.1*np.amin(s), 2*np.amax(s)])
plt.xlabel('time (s)')
plt.ylabel('current (nA)')
plt.title('Subplot 1: \n Gaussian colored noise')
# this is an inset axes over the main axes
inset_axes = inset_axes(ax,
width="50%", # width = 30% of parent_bbox
height=1.0, # height : 1 inch
loc=1)
n, bins, patches = plt.hist(s, 400, normed=1)
#plt.title('Probability')
plt.xticks([])
plt.yticks([])
ax = fig.add_subplot(122)
# the main axes is subplot(111) by default
plt.plot(t, s)
plt.axis([0, 1, 1.1*np.amin(s), 2*np.amax(s)])
plt.xlabel('time (s)')
plt.ylabel('current (nA)')
plt.title('Subplot 2: \n Gaussian colored noise')
plt.tight_layout()
plt.show()