Skip to content Skip to sidebar Skip to footer

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()

enter image description here

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:

out.gif

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?"