简体   繁体   English

为什么matplotlib.figure.Figure的行为与matplotlib.pyplot.figure有很大不同

[英]Why does matplotlib.figure.Figure behave so different than matplotlib.pyplot.figure

A fellow programmer alerted me to a problem where matplotlib.pyplot and Tkinter don't behave well together, as demonstrated by this question Tkinter/Matplotlib backend conflict causes infinite mainloop 一位同事程序员提醒我一个matplotlib.pyplot和Tkinter不能很好地协同工作的问题,正如这个问题所证明的那样Tkinter / Matplotlib后端冲突导致无限主循环

We changed our code to prevent potential problems as mentioned in the linked question, as follows: 我们更改了代码以防止链接问题中提到的潜在问题,如下所示:

Old

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

Intermediate (UI.png not being shown) 中级(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()

The changed code did not display the 'background' image anymore and I have been mostly just trying random things (as I am quite lost in the difference between the two options) to get the figure displaying again. 改变后的代码不再显示“背景”图像,而且我大多只是尝试随机的事情(因为我在两个选项之间的差异中完全迷失)才能再次显示图形。 The changes involved switching from tight_layout to set_tight_layout to avoid a warning, as mentioned on https://github.com/matplotlib/matplotlib/issues/1852 . 这些更改涉及从tight_layout切换到set_tight_layout以避免警告,如https://github.com/matplotlib/matplotlib/issues/1852所述 The resulting code is as follows: 结果代码如下:

Potential Fix 潜在的修复

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

The question therefore is, why do we need to use a subplot now (using matplotlib.figure.Figure) while before we did not (using matplotlib.pyplot)? 因此,问题是,为什么我们现在需要使用子图(使用matplotlib.figure.Figure)而不是之前(使用matplotlib.pyplot)?

PS: I am sorry if this is a silly question but almost everything that I can find on the subject seems to use the matplotlib.pyplot variant. PS:我很抱歉,如果这是一个愚蠢的问题,但几乎我在这个问题上找到的所有内容似乎都使用了matplotlib.pyplot变体。 Therefore, I am having trouble finding any good documentation for the matplotlib.figure.Figure variant. 因此,我无法找到matplotlib.figure.Figure变体的任何好文档。

TL;DR TL; DR

The question therefore is, why do we need to use a subplot now (using matplotlib.figure.Figure) while before we did not (using matplotlib.pyplot)? 因此,问题是,为什么我们现在需要使用子图(使用matplotlib.figure.Figure)而不是之前(使用matplotlib.pyplot)?

subplot creates an Axes object. subplot创建一个Axes对象。 You did have one before, but the pyplot API "hid" it from you under the covers so you didn't realise it. 你之前确实有过一个,但是pyplot API“隐藏”了你的封面所以你没有意识到它。 You are now trying to use the objects directly, so have to handle it yourself. 您现在正在尝试直接使用这些对象,因此必须自己处理它。

More detailed reason 更详细的原因

The reason you see this behaviour is because of how matplotlib.pyplot works. 您看到此行为的原因是因为matplotlib.pyplot工作原理。 To quote the tutorial a little: 引用教程一点:

matplotlib.pyplot is a collection of command style functions that make matplotlib work like MATLAB.... matplotlib.pyplot is stateful, in that it keeps track of the current figure and plotting area, and the plotting functions are directed to the current axes matplotlib.pyplot是命令样式函数的集合,它使matplotlib像MATLAB一样工作.... matplotlib.pyplot是有状态的,因为它跟踪当前的图形和绘图区域,绘图函数指向当前轴

The crucial bit is that pyplot is stateful. 关键是pyplot是有状态的。 It is keeping track of state "under the covers" and hiding the object model from you to some extent. 它正在跟踪状态“隐藏”并在某种程度上隐藏对象模型。 It also does some implicit things. 它也做了一些隐含的事情。 So - if you simply call, eg, plt.axis() , under the covers pyplot calls plt.gca() and that in turn calls gcf() which will return a new figure , because you haven't set up a figure via pyplot yet. 所以 - 如果你只是调用,例如, plt.axis() ,在封面下pyplot调用plt.gca() ,然后调用gcf() ,这将返回一个新的数字 ,因为你还没有设置一个数字pyplot呢。 This is true for most calls to plt.some_function() - if pyplot doesn't have a figure object in it's own state yet, it will create one. 大多数对plt.some_function()调用都是如此 - 如果pyplot 在它自己的状态中没有一个figure对象,它将创建一个。

So, in your intermediate example, you've created your own Figure object - fiven it a name self.fig (I'm not sure what your class structure is, so I don't know what self is, but I'm guessing it's your tk.Frame object or something similar). 所以,在你的中间例子中,你已经创建了自己的Figure对象 - 这就是一个名字self.fig (我不确定你的类结构是什么,所以我不知道self是什么,但我猜它是你的tk.Frame对象或类似的东西)。

The punchline 妙语

pyplot doesn't know anything about self.fig . pyplot self.fig So in your intermediate code, you're calling imshow() on the Figure object in pyplot state, but displaying a different figure ( self.fig ) on your canvas. 因此,在您的中间代码中,您在pyplot状态下调用Figure对象上的imshow() ,但在画布上显示不同的图形( self.fig )。

The problem is not that you need to use subplot as such, but that you need to change the background image on the correct Figure object. 问题不是您需要使用subplot ,而是需要更改正确的Figure对象上的背景图像。 The way you've used subplot in your potential fix code will do that - although I suggest an alternative below which maybe makes the intent clearer. 你在潜在的修复代码中使用subplot的方式就是这样 - 虽然我建议在下面的替代方案可能会使意图更清晰。

How to fix 怎么修

Change 更改

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)

A note on root cause: pyplot API vs direct use of objects 关于根本原因的说明: pyplot API与直接使用对象

This a bit of an opinion, but might help. 这有点意见,但可能有所帮助。 I tend to use the pyplot interface when I need to quickly get things prototyped and want to use one of the fairly standard cases. 当我需要快速获取原型并希望使用其中一个相当标准的情况时,我倾向于使用pyplot接口。 Often, that is enough. 通常,这就足够了。

As soon as I need to do more complicated things, I start to use the object model directly - maintaining my own named Figure and Axes objects etc. 一旦我需要做更复杂的事情,我就开始直接使用对象模型 - 维护我自己的名为FigureAxes对象等。

Mixing the two is possible, but often confusing. 混合这两者是可能的,但往往令人困惑。 You've found this with your intermediate solution. 您已经在中间解决方案中找到了这个。 So I recommend doing one or the other. 所以我建议做一个或另一个。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM