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
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)
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 . 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)?
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. Therefore, I am having trouble finding any good documentation for the matplotlib.figure.Figure
variant.
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)?
subplot
creates an Axes
object. You did have one before, but the pyplot
API "hid" it from you under the covers so you didn't realise it. You are now trying to use the objects directly, so have to handle it yourself.
The reason you see this behaviour is because of how matplotlib.pyplot
works. 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
The crucial bit is that pyplot
is stateful. 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. 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.
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).
pyplot
doesn't know anything about 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.
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. 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.
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)
pyplot
API vs direct use of objects 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. 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.
Mixing the two is possible, but often confusing. You've found this with your intermediate solution. So I recommend doing one or the other.
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.