What I want to do is:
With the example code below, I got to this (matplotlib 3.1.2, Python3 in MINGW64, Windows 10):
As it is shown on the gif - even in the starting state, there is some empty space on the right (since I didn't know any better way how to solve this, other than define a grid). Then, when the figure window extends/resizes, it is not "exactly" resized so it fits the third plot.
How can I achieve a toggling of this third plot, such that when it is hidden, there is no extra empty space on the right - and when it is shown, the figure extends exactly so the third plot fits (including margins) (EDIT: and the existing/initial two plots do not change in size)?
The code:
#!/usr/bin/env python3
import matplotlib
print("matplotlib.__version__ {}".format(matplotlib.__version__))
import matplotlib.pyplot as plt
import numpy as np
default_size_inch = (9, 6)
showThird = False
def onpress(event):
global fig, ax1, ax2, ax3, showThird
if event.key == 'x':
showThird = not showThird
if showThird:
fig.set_size_inches(default_size_inch[0]+3, default_size_inch[1], forward=True)
plt.subplots_adjust(right=0.85) # leave a bit of space on the right
ax3.set_visible(True)
ax3.set_axis_on()
else:
fig.set_size_inches(default_size_inch[0], default_size_inch[1], forward=True)
plt.subplots_adjust(right=0.9) # default
ax3.set_visible(False)
ax3.set_axis_off()
fig.canvas.draw()
def main():
global fig, ax1, ax2, ax3
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*np.pi/4)
fig = plt.figure(figsize=default_size_inch, dpi=120)
ax1 = plt.subplot2grid((3,3), (0,0), colspan=2, rowspan=2)
ax2 = plt.subplot2grid((3,3), (2,0), colspan=2, sharex=ax1)
ax3 = plt.subplot2grid((3,3), (0,2), rowspan=3)
ax3.set_visible(False)
ax3.set_axis_off()
ax1.plot(xdata, ydata1, color="Red")
ax2.plot(xdata, ydata2, color="Khaki")
fig.canvas.mpl_connect('key_press_event', lambda event: onpress(event))
plt.show()
# ENTRY POINT
if __name__ == '__main__':
main()
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()
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 python3
import 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 = False
def onpress(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' not in globals():
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()
def main():
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 POINT
if __name__ == '__main__':
main()
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.