Interactively Resize Figure And Toggle Plot Visibility In Matplotlib?
What I want to do is: Start the figure with two subplots (stacked one above another) Press 'x' on keyboard to: resize figure, and show a third plot on the right Press 'x' again to
Solution 1:
As commented, you essentially have two options; use a single gridspec, or use two, one for each state. Let's look at the first option, using a single gridspec. To this end you would first define all needed parameters in inches, then calculate the subplot parameters (in relative units) for each of the two desired states.
When pressing x you would toggle between the states by updating the gridspec parameters via .update()
.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
w,h = plt.rcParams["figure.figsize"]
# Define dimensions in inches (could also just put numbers here)
left = plt.rcParams["figure.subplot.left"] * w
right = (1 - plt.rcParams["figure.subplot.right"]) * w
wspace = plt.rcParams["figure.subplot.wspace"] * w
figw1, figh1 = (7,5)
ax1width = figw1 - left - right
ax2width = 3.5
#calculate remaining free parameter, the figure width of the enlarged figure
figh2 = figh1
figw2 = left + ax1width + wspace + ax2width + right
#calculate subplot parameters for both cases
subplotpars1 = dict(left = left/figw1, right=(left + ax1width + wspace + ax2width)/figw1,
wspace=wspace/(ax1width+ax2width), )
subplotpars2 = dict(left = left/figw2, right=(left + ax1width + wspace + ax2width)/figw2,
wspace=wspace/(ax1width+ax2width), )
# create GridSpec
gs = GridSpec(2,2, width_ratios=(ax1width, ax2width), **subplotpars1)
# Create figure with 3 axes
fig = plt.figure(figsize=(figw1, figh1))
ax1 = fig.add_subplot(gs[0,0])
ax2 = fig.add_subplot(gs[1,0])
ax3 = fig.add_subplot(gs[:,1])
ax1.plot([2,4], color="C0")
ax2.plot([0,11], color="C1")
ax3.plot([5,15], color="C2")
# Updating machinery
current_state = [0]
subplotspars = [subplotpars1, subplotpars2]
figsizes = [(figw1, figh1), (figw2, figh2)]
def key_press(evt):
if evt.key == "x":
current_state[0] = (current_state[0] + 1) % 2
gs.update(**subplotspars[current_state[0]])
fig.set_size_inches(figsizes[current_state[0]], forward=True)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("key_press_event", key_press)
plt.show()
Solution 2:
Here is an example that preserves the width of the initial plot - and attempts to scale the figure size once the plot on the right is added; however, since the margins are expressed relatively, and also there is a space between supblots, the main plot "moves" which I don't really want:
Code:
#!/usr/bin/env python3import matplotlib
print("matplotlib.__version__ {}".format(matplotlib.__version__))
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
default_size_inch = (9, 6)
showThird = Falsedefonpress(event):
global fig, ax1, ax2, ax3, showThird, gs1, gs2, xdata, main_plot_width_rel
if event.key == 'x':
showThird = not showThird
if showThird:
ax1.set_position(gs2[:-1, :-1].get_position(fig))
ax2.set_position(gs2[2, 0:-1].get_position(fig))
if'ax3'notinglobals():
ax3 = fig.add_subplot(gs2[:, 2])
ax3.plot(xdata, xdata, color="Green")
ax3.set_visible(True)
ax3.set_axis_on()
ax3.set_position(gs2[:, 2].get_position(fig))
print(vars(fig.subplotpars)) # does not change: {'validate': True, 'left': 0.125, 'right': 0.9, 'bottom': 0.11, 'top': 0.88, 'wspace': 0.2, 'hspace': 0.2}print(ax1.get_position()) # Bbox(x0=0.125, y0=0.3817647058823529, x1=0.6264705882352941, y1=0.88)print(ax2.get_position()) # Bbox(x0=0.125, y0=0.10999999999999999, x1=0.6264705882352941, y1=0.3364705882352941)print(ax3.get_position()) # Bbox(x0=0.6720588235294118, y0=0.10999999999999999, x1=0.9000000000000001, y1=0.88)
main_plot_width_rel_third = ax1.get_position().x1 - ax1.get_position().x0
widthfactor = main_plot_width_rel/main_plot_width_rel_third
new_width_inch = default_size_inch[0]*widthfactor
print(main_plot_width_rel_third, widthfactor, new_width_inch) # 0.5014705882352941 1.5454545454545452 13.909090909090907
fig.set_size_inches(new_width_inch, default_size_inch[1], forward=True)
else:
ax3.set_visible(False)
ax3.set_axis_off()
ax1.set_position(gs1[:-1, :].get_position(fig))
ax2.set_position(gs1[2, 0:2].get_position(fig))
print(vars(fig.subplotpars)) # does not change: {'validate': True, 'left': 0.125, 'right': 0.9, 'bottom': 0.11, 'top': 0.88, 'wspace': 0.2, 'hspace': 0.2}print(ax1.get_position()) # Bbox(x0=0.125, y0=0.3817647058823529, x1=0.8999999999999999, y1=0.88)print(ax2.get_position()) # Bbox(x0=0.125, y0=0.10999999999999999, x1=0.8999999999999999, y1=0.3364705882352941)
fig.set_size_inches(default_size_inch[0], default_size_inch[1], forward=True)
fig.canvas.draw()
defmain():
global fig, ax1, ax2, ax3, gs1, gs2, xdata, main_plot_width_rel
xdata = np.arange(0, 101, 1) # 0 to 100, both included
ydata1 = np.sin(0.01*xdata*np.pi/2)
ydata2 = 10*np.sin(0.01*xdata*3*np.pi/4)
# https://stackoverflow.com/questions/43937066/
fig = plt.figure(figsize=default_size_inch, dpi=120)
gs1 = gridspec.GridSpec(3, 2, figure=fig) # , height_ratios=[5, 2, 1], hspace=0.3
gs2 = gridspec.GridSpec(3, 3, figure=fig) # , height_ratios=[5,3]# instead of colspan, specify array elements ranges - see:# https://matplotlib.org/3.1.1/gallery/subplots_axes_and_figures/gridspec_multicolumn.html# note that e.g. "column 0 and 1" is specified as `0:2` (or, if that is all columns, just `:`); -1 is "last element"
ax1 = fig.add_subplot(gs1[:-1, :])
ax2 = fig.add_subplot(gs1[2, 0:2], sharex=ax1)
ax1.plot(xdata, ydata1, color="Red")
ax2.plot(xdata, ydata2, color="Khaki")
# fig.subplotpars => SubplotParams: "All dimensions are fractions of the figure width or height"print(vars(fig.subplotpars)) # {'validate': True, 'left': 0.125, 'right': 0.9, 'bottom': 0.11, 'top': 0.88, 'wspace': 0.2, 'hspace': 0.2}#print(ax1.rect) # 'AxesSubplot' object has no attribute 'rect'#print(ax1.position) # 'AxesSubplot' object has no attribute 'position'# SO:58992207#print(ax1.get_rect()) # AttributeError: 'AxesSubplot' object has no attribute 'get_rect'print(fig.get_size_inches()) # [9. 6.]print(ax1.get_position()) # Bbox(x0=0.125, y0=0.3817647058823529, x1=0.8999999999999999, y1=0.88)print(ax2.get_position()) # Bbox(x0=0.125, y0=0.10999999999999999, x1=0.8999999999999999, y1=0.3364705882352941)#main_plot_width_rel = fig.subplotpars.right - fig.subplotpars.left
main_plot_width_rel = ax1.get_position().x1 - ax1.get_position().x0
main_plot_width_inch = main_plot_width_rel*fig.get_size_inches()[0]
print(main_plot_width_rel, main_plot_width_inch) # 0.7749999999999999 6.975 (rather: 0.775 6.975)
fig.canvas.mpl_connect('key_press_event', lambda event: onpress(event))
#fig.canvas.draw()
plt.show()
# ENTRY POINTif __name__ == '__main__':
main()
Post a Comment for "Interactively Resize Figure And Toggle Plot Visibility In Matplotlib?"