Why does matplotlib.figure.Figure behave as much as matplotlib.pyplot.figure
One programmer warned me about an issue where matplotlib.pyplot and Tkinter do not behave well together as shown in this question Tkinter / Matplotlib conflict calls infinite mainloop
We modified our code to prevent the possible problems 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 showing)
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 modified code no longer displays the "background" image, and I basically just tried randomly (since I lost the difference between the two) to display the shape again. The changes included moving from tight_layout
to set_tight_layout
to avoid the warning as pointed out at https://github.com/matplotlib/matplotlib/issues/1852 . The resulting code looks like this:
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()
So the question is, why do we now use a subplot (using matplotlib.figure.Figure), and before that we did not use (using matplotlib.pyplot)?
PS: I am sorry if this is a stupid question, but almost everything I can find on this question seems to use a variant matplotlib.pyplot
. So I am having trouble finding good documentation for the option matplotlib.figure.Figure
.
source to share
TL; DR
So the question is, why do we now use a subplot (using matplotlib.figure.Figure), and before that we did not use (using matplotlib.pyplot)?
subplot
creates an object Axes
. You had one, but the API pyplot
"hid" it under your covers so that you don't realize it. You are now trying to use objects directly, so they will have to handle them themselves.
More detailed reason
The reason you see this behavior has to do with how it works matplotlib.pyplot
. To quote the tutorial :
matplotlib.pyplot
is a set of command-style functions that make matplotlib work like MATLAB ....matplotlib.pyplot
is discreet as it keeps track of the current digit and plot area and the plotting functions are directed to the current axes
The key bit is what pyplot
is consistent. It keeps track of under-the-hood state and hides the object model from you to some extent. It does some implicit things as well. So - if you just call, for example, plt.axis()
under the covers calls and this in turn calls , which returns a new figure , because you don't have to customize the digit with . This is true for most calls - if it doesn't have a curly object in its own state , it will create it. pyplot
plt.gca()
gcf()
pyplot
plt.some_function()
pyplot
So, in your intermediate example, you created your own object Figure
- gave it a name self.fig
(I'm not sure what the structure of your class is, so I don't know which self
is, but I'm assuming it's your object tk.Frame
or something similar).
Dotted line
pyplot
knows nothing aboutself.fig
. So, in your intermediate code, you are calling an imshow()
object Figure
in a state pyplot
, but displaying a different number ( self.fig
) on your canvas .
The problem is not that you need to use subplot
as such, but you need to change the background image to the correct object Figure
. The way you have used subplot
the fix in your potential code will do it, although I suggest an alternative below which it is possible to make the intent clearer.
How to fix
Edit
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)
Main reason note: pyplot
API versus direct use of objects
This is a bit of an opinion, but might help. I usually use an interface pyplot
when I need to get prototypes quickly and want to use one of the fairly standard cases. This is often enough.
As soon as I need to do more complicated things, I'm starting to use the object model directly - keeping their own objects with the name Figure
and Axes
etc.
Mixing the two is possible, but often confusing. You found this with your intermediate solution. So I recommend doing this or that.
source to share