I don't understand how figures created with matplotlib are shown, when they are updated, when they are blocking, etc.
To help my understanding and to help me make matplotlib do what I need specifically, could anyone help me create a matplotlib.figure
wrapper/almost-clone, let's call it MyFigure, that behaves exactly as described in the code (and its comments) below?
X=[0,1]
Y=[0,1]
Y2a=[0,2]
Y2b=[0,2.5]
Y3a=[0,3]
Y3b=[0,3.5]
Y4=[0,4]
fig = MyFigure() # Nothing happens
ax=fig.gca() # Nothing happens
ax.plot(X,Y) # Nothing happens
fig.show() # New window (1) appears and shows plot of X,Y; execution continues directly
ax.set_xlabel('X') # Window 1 gets X axis label
ax.set_ylabel('Y') # Window 1 gets Y axis label
fig.hide() # Window 1 disappears
time.sleep(10) # No open windows for 10 seconds
fig.show() # Window 1 reappears and looks the same as before
fig2 = MyFigure() # Nothing happens
fig2.show() # New window (2) appears and is empty
ax2 = fig2.gca() # Window 2 shows axes
ax2.plot(X,Y2a) # Window 2 shows X,Y2a
fig2.show() # Nothing happens, could be omitted
time.sleep(60) # Long computation. In the meantime, windows 1 and 2 can still be resized and their controls (zoom etc.) can be used
ax2.plot(X,Y2b) # Window 2 shows X,Y2a as well as X,Y2b
fig2.hide() # Window 2 disappears
fig3 = MyFigure() # Nothing happens
ax3 = fig3.gca() # Nothing happens
ax3.plot(X,Y3a) # Nothing happens
fig3.show() # Window 3 appears and shows plot of X,Y3a; execution continues
fig3.freeze() # Nothing happens, Window 3 is still open and can be manipulated
ax3.set_xlabel('X') # Nothing happens
ax3.set_ylabel('Y') # Nothing happens
fig3.thaw() # Window 3 gets X and Y axis labels
fig3.clear() # Window 3 is still open but empty
ax3 = fig3.gca() # Window 3 shows empty axes
ax3.plot(X,Y3b) # Window 3 shows plot of X,Y3b
fig4 = MyFigure() # Nothing happens
ax4 = fig4.gca() # Nothing happens
ax4.plot(X,Y4) # Nothing happens
fig4.show(block=True) # Window 4 opens and shows X,Y4. Execution pauses
print('Window 4 was closed by user') # Happens after user closes window 4
fig4.show() # Window 4 reappears and looks as before
fig4.close() # Window 4 disappears and its resources are freed
try:
fig4.show()
except Exception:
print('Something went wrong') # Prints 'Something went wrong'
# Windows 1,2,3 still "exist".
# Window 2 is not visible but could be manipulated and shown again in later code.
# Windows 1 and 3 are still visible and can be manipulated by the user.
# They disappear as soon as the objects fig and fig3 are garbage collected,
# or their `hide` or `close` methods are called.
Note that the call to MyFigure().show()
can do anything you need it to in order to get things work as I describe, I just used the word show
because it's similar to what plt.show
already does.
I have heard of at least three ways that could help with some of what I want regarding interactivity (plt.ion()
, fig.show(block=False)
, plt.draw()
; three and a half if you count ipython
's magic commands, which I don't intend to use), yet none of these really work exactly as I'd hope and I don't really understand the logical model behind any of them. See the bottom of this question for my failed attempts.
I do not want to discuss or criticize the way matplotlib figures and their methods work by default. I'm sure there are good reasons, technical and functional, for the way they work, but its simply not compatible with my intuition (fed from no real GUI experience elsewhere) and I get frustrated with it anytime I have to use it before I get to learning anything beyond simple plot commands. I hope to change that.
Basically, I imagine MyFigure
instances as representing threads with two important boolean instance variables: visible
and frozen
. The first one determines whether an open window is associated to the instance and can be set by show()
and hide()
; the second one controls whether changes (such as new plots) are added to the drawing area immediately or to a queue, and can be set by freeze()
and thaw()
.
The call show(block=False)
would only returns once the corresponding window is closed by the user (or by another thread with access to the MyFigure instance). It can be called no matter the current value of visible
and will result in visible=False
. The call thaw()
would set frozen=False
and applies the change queue to the figure (this could be applied even when visible=False
, though it wouldn't make sense to freeze and unfreeze the figure in that state, unless the figure is manipulated in another thread in the meanwhile).
Most important to me is the behavior of show()
and interactive behavior of the instances; I mostly described the freeze
behavior in anticipation of questions how that use case would be handled in my setup. I don't like the ax=fig.gca()
behavior and would feel better with something like fig.add(matplotlib.Axes())
but that's not relevant to this question (besides, I'm confident I can figure this out myself).
To clarify my struggles, let me record my failed attempts so far:
plt.ion()
I prepend my code with
def MyFigure():
plt.ion()
return plt.figure()
I comment out block one and continue at fig2 = ...
.
When the execution hits time.sleep(60)
, there is no open window for 60 seconds.
fig.show(block=False)
I remember this working on the laptop where I first wrote this post, but the block keyword seems to have been removed in the newer version of matplotlib that I am using on my desktop computer
plt.draw()
This affects all figures equally. Trying to call draw
as an instance method of fig
results in TypeError: draw_wrapper() missing 1 required positional argument: 'renderer'
.
Ignoring this problem for the moment, plt.draw()
in itself doesn't do anything. Based on some internet research, I found out that a subsequent plt.pause(0.01)
does what I want. This is actually the solution that comes closest to what I want. Block two works exactly as desired (ignoring the final fig.hide()
) if I replace all fig.show()
by plt.draw();plt.pause(0.01)
, but it seems beyond ugly and also does not seem what the internet recommends.
(To potential close voters: I could formulate a number of specific questions for each of the lines in the code. However, I feel like all the desired behaviour is (a) related enough to be presented together (b) best described in the form of code that is wish to write together with a comment of what I'd wish it didn't.)