【问题标题】:Interactively resize figure and toggle plot visibility in Matplotlib?在 Matplotlib 中交互式调整图形大小和切换绘图可见性?
【发布时间】:2019-11-21 15:26:09
【问题描述】:

我想做的是:

  • 以两个子图开始图(堆叠在另一个之上)
  • 按键盘上的“x”可:调整图形大小,并在右侧显示第三个图
  • 再次按“x”可以:将图形调整回原来的大小,并隐藏第三个绘图(不为第三个绘图留出空间)。

通过下面的示例代码,我得到了这个(matplotlib 3.1.2,MINGW64 中的 Python3,Windows 10):

正如 gif 所示 - 即使在开始状态下,右侧也有一些空白区域(因为我不知道如何解决这个问题,除了定义一个网格之外)。然后,当图形窗口扩展/调整大小时,它不会“完全”调整大小,因此它适合第三个图。

我怎样才能实现第三个图的切换,这样当它被隐藏时,右侧没有额外的空白空间 - 当它显示时,图形完全延伸,因此第三个图适合(包括边距) (编辑:现有/初始两个地块的大小没有变化)?

代码:

#!/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()

【问题讨论】:

  • 您需要决定是否要使用一个或两个 gridspec,每个州一个。在第一种情况下,您有方程 margin_left1 + axwidth1 + space + axwidth2 + margin_right1 = figwidth1margin_left2 + axwidth1 + space + axwidth2 + margin_right2 = figwidth2。在第二种情况下,您有margin_left1 + axwidth1 + margin_right1 = figwidth1margin_left2 + axwidth1 + space + axwidth2 + margin_right2 = figwidth2。从那些你需要计算要应用的子图参数中。
  • @ImportanceOfBeingErnest - 是否有可能以某种方式“更改”gridspec,而不更改 ax1/ax2 并(重绘)其中的图?编辑:我想我找到了你所说的一个例子(你的):stackoverflow.com/questions/43937066/…
  • 是的,可以通过.update 更改网格规范。也可以使用两种不同的网格规范,如该示例所示。

标签: python matplotlib plot


【解决方案1】:

正如评论的那样,您基本上有两个选择;使用一个 gridspec,或者使用两个,每个状态一个。让我们看看第一个选项,使用单个 gridspec。为此,您将首先以英寸为单位定义所有需要的参数,然后为两个所需状态中的每一个计算子图参数(以相对单位为单位)。

按下 x 时,您可以通过.update() 更新 gridspec 参数来切换状态。

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

【讨论】:

  • 非常感谢,看起来很棒!我想,我遇到的困难是“您首先要以英寸为单位定义所有需要的参数”对我来说并不是很明显 - 再次感谢您提供示例!
【解决方案2】:

这是一个保留初始绘图宽度的示例 - 并在添加右侧绘图后尝试缩放图形大小;但是,由于边距是相对表达的,并且在 suplots 之间也有空格,所以我并不真正想要的主要情节“移动”:

代码:

#!/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()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-26
    • 2018-04-28
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    • 2021-01-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多