为什么matplotlib.figure.Figure的行为与matplotlib.pyplot.figure如此不同?

3
一个程序员向我报告了一个问题,即matplotlib.pyplot和Tkinter不能很好地配合使用,就像这个问题所示Tkinter/Matplotlib backend conflict causes infinite mainloop 为预防潜在问题,我们更改了代码,如下所示:

旧版本

import matplotlib.pyplot as plt
self.fig = plt.figure(figsize=(8,6))
if os.path.isfile('./UI.png'):
    image = plt.imread('./UI.png')
    plt.axis('off')
    plt.tight_layout()
    im = plt.imshow(image)
# The Canvas
self.canvas = FigureCanvasTkAgg(self.fig, master = master)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, root)
self.canvas.get_tk_widget().pack(fill=BOTH,expand=YES)
self.canvas.draw()

中级(UI.png未显示)
import matplotlib.pyplot as plt
import matplotlib
self.fig = matplotlib.figure.Figure(figsize=(8, 6))
if os.path.isfile('./UI.png'):
    image = matplotlib.image.imread('./UI.png')
    plt.axis('off')
    plt.tight_layout()
    plt.imshow(image)
# The Canvas
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, root)
self.canvas.get_tk_widget().pack(fill=BOTH, expand=YES)
self.canvas.draw()

修改代码后,'background'图片不再显示,我一直在尝试一些随机的方法(因为我很迷失于两个选项之间的差异),以便再次显示该图。修改涉及从tight_layout转换到set_tight_layout,以避免警告,如https://github.com/matplotlib/matplotlib/issues/1852所述。最终的代码如下:

潜在解决方案

import matplotlib.pyplot as plt
import matplotlib
self.fig = matplotlib.figure.Figure(figsize=(8, 6))
background_image = self.fig.add_subplot(111)
if os.path.isfile('./UI.png'):
    image = matplotlib.image.imread('./UI.png')
    background_image.axis('off')
    #self.fig.tight_layout() # This throws a warning and falls back to Agg renderer, 'avoided' by using the line below this one.
    self.fig.set_tight_layout(True)
    background_image.imshow(image)
# The Canvas
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, root)
self.canvas.get_tk_widget().pack(fill=BOTH, expand=YES)
self.canvas.draw()

因此,问题是,为什么我们现在需要使用一个子图(使用matplotlib.figure.Figure)而之前没有(使用matplotlib.pyplot)?
PS:如果这个问题很傻,请原谅,但几乎我能找到的所有关于这个主题的东西似乎都使用matplotlib.pyplot变量。因此,我很难找到任何关于matplotlib.figure.Figure变量的好的文档。
1个回答

2

简而言之

问题是,为什么我们现在需要使用一个subplot(使用matplotlib.figure.Figure),而以前我们不需要(使用matplotlib.pyplot)?

subplot创建了一个Axes对象。 以前你已经有了一个,但是pyplot API将其隐藏在封面下,所以你没有意识到它。 现在你正在尝试直接使用对象,因此必须自己处理。

更详细的原因

您看到这种行为的原因是由于matplotlib.pyplot的工作方式。 引用教程中的一句话:

matplotlib.pyplot是一组命令样式函数,使matplotlib像MATLAB一样工作.... matplotlib.pyplot是有状态的,因为它跟踪当前图和绘图区域,并且绘图函数被定向到当前轴

关键部分是pyplot是有状态的。 它在“封面下”跟踪状态并在某种程度上隐藏对象模型。 它还做一些隐含的事情。 因此-如果您只是调用例如plt.axis(),在封面下pyplot调用plt.gca(),然后调用gcf(),该函数将返回一个新图,因为您还没有通过pyplot设置图。 对于大多数对plt.some_function()的调用都是如此-如果pyplot在自己的状态中没有图形对象,则会创建一个。

因此,在您的中间示例中,您已经创建了自己的Figure对象-给它起了一个名字self.fig(我不确定您的类结构是什么,所以我不知道self是什么,但我猜测它是您的tk.Frame对象或类似的东西)。

重点

"pyplot不知道self.fig是什么。因此,在你的中间代码中,你在pyplot状态下调用Figure对象中的imshow(),但在你的画布上显示了另一个图像(self.fig)。
问题不在于你需要使用subplot,而是你需要在正确的Figure对象上更改背景图像。你在潜在的修复代码中使用subplot的方式就可以做到这一点,尽管我建议下面的替代方案可能会使意图更加清晰。
如何修复:
更改"
plt.axis('off')
plt.tight_layout()
plt.imshow(image)

to

self.fig.set_tight_layout(True)
ax = self.fig.gca() # You could use subplot here to get an Axes object instead
ax.axis('off')
ax.imshow(image)

关于根本原因的注释: pyplot API与直接使用对象的区别

这是我的一些意见,但可能有所帮助。当我需要快速创建原型并想要使用其中一个相当标准的情况时,我倾向于使用pyplot接口。通常,那就足够了。

一旦我需要做更复杂的事情,我就开始直接使用对象模型 - 维护自己命名的FigureAxes对象等。

混合两者是可能的,但经常令人困惑。你在中间解决方案中发现了这一点。所以我建议只使用其中之一。


很棒的答案。你认为哪个更好,使用subplot变量(就像我偶然发现的那样)还是fig.gca()(就像你在修复中列出的那样)? - Bas Jansen
1
在这种情况下,我认为gca()更清晰易读 - 我总是认为subplot可能会有多个。但是,这确实取决于个人喜好 - 两者都可以。如果你在gca()之后执行print(type(ax)),你会看到它的具体类型实际上是AxesSubplotAxes的子类) - 与add_subplot()返回的完全相同。 - J Richard Snape

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接