Button to next display in matplotlib - python

I have a class object with an attribute display(self):
import matplotlib.pyplot as plt
class Obj:
def display(self) -> None:
fig = plt.figure()
sub = fig.add_subplot()
sub.plot(...)
plt.show()
def dostuff(self) -> 'stuff':
...
self.display()
...
self.display()
...
self.display()
return
I use this function to have a better visual reference of how my dostuff(self) attribute is handling its task. It all works as intended, when the self.display() command is registered the scrypt pauses execution and plots stuff. However, to resume the only way is closing the matplotlib window manually and then the program reopens another one with the next changes.
Is there a way to implement a button or a better way to view the next changes without having to close and reopen a new window every single time?

plt.show has parameter block. If you set it to False, then the execution is not blocked. Hope this helps.

Related

Confusion with matplotlib timers

I tried to implement the workaround mentioned here to the problem of the figure not updating properly when a draw_event is triggered once a user zooms into a plot. This was meant to improve this answer. In the solution, a Timer is added to the Canvas of the Figure, which delays the draw function for a short while.
In the linked answer this is done for a single figure and a reference to the timer is stored as self.timer to the class that contains the update function. I would like to have an a bit more general behaviour and allow this for many Figure instances. Therefore I tried not to save the Timer reference (I don't need it anymore). But when I do so, the script crashes with a Segmentation Fault (no traceback). Here is my code:
from matplotlib import pyplot as plt
import numpy as np
##plt.switch_backend('TkAgg')
class DrawEventHandler:
def __init__(self):
x = np.linspace(0,1,10)
y = np.sin(x)
self.fig, self.ax = plt.subplots()
self.ax.plot(x,y)
self.ax.figure.canvas.mpl_connect('draw_event', self.update)
def update(self, event = None):
self._redraw_later_ok()
##self._redraw_later_not_ok(self.fig)
def _redraw_later_ok(self):
self.timer = self.fig.canvas.new_timer(interval=10)
self.timer.single_shot = True
self.timer.add_callback(lambda : self.fig.canvas.draw_idle())
self.timer.start()
def _redraw_later_not_ok(self, fig):
print('start')
timer = fig.canvas.new_timer(interval=10)
timer.single_shot = True
timer.add_callback(lambda : fig.canvas.draw_idle())
timer.start()
print('stop')
if __name__ == '__main__':
my_handler = DrawEventHandler()
plt.show()
The original solution is implemented as _redraw_later_ok(), which works fine for me. The problematic solution is called `_redraw_later_not_ok()', which produces this output:
start
stop
Segmentation fault: 11
I use a Mac with High Sierra, Python 3.6.4 or Python 2.7.14 and Matplotlib 2.1.1 with the MacOSX backend. When I switch to the TkAgg backend (which is terribly slow), the code works fine. Can anybody explain what is going on?
As usual with interactive features which use the event loop of a GUI you need to keep a reference to the object that handles the callback.
The problem with the approach of not storing the timer in this code
def _redraw_later_not_ok(self, fig):
print('start')
timer = fig.canvas.new_timer(interval=10)
timer.single_shot = True
timer.add_callback(lambda : fig.canvas.draw_idle())
timer.start()
print('stop')
is that timer gets garbage collected once the _redraw_later_not_ok function terminates (i.e. directly after stop is printed). At this point there would be a pointer to a place in memory, which may or may not store the callback (any more). An attempt by the python interpreter to call the callback would hence (most probably) fail.
The solution is indeed to always keep a reference to the object that steers the callback. This is the solution shown in _redraw_later_ok, where the timer is made a class attribute, self.timer. Such that it can later be used at the time the callback is called.
I do not understand in how far using this working approach would prevent the use of several figures, so it may make sense to create a MWE with two figures that shows the problem clearly.

Python Matplotlib: Clear figure when figure window is not open

I'm working with matplotlib plotting and use ioff() to switch interactive mode off to suppress the automatic opening of the plotting window on figrue creation. I want to have full control over the figure and only see it when explicitely using the show() command.
Now apparently the built-in commands to clear figures and axes do not work properly anymore.
Example:
import numpy as np
import matplotlib.pyplot as mpp
class PlotTest:
def __init__(self,nx=1,ny=1):
# Switch off interactive mode:
mpp.ioff()
# Create Figure and Axes:
self.createFigure(nx, ny)
def createFigure(self,nx=1,ny=1):
self.fig, self.axes = mpp.subplots(nx,ny)
if nx*ny == 1:
self.axes = np.array([self.axes])
def linePlot(self):
X = np.linspace(0,20,21)
Y = np.random.rand(21)
self.axes[0].plot(X,Y)
P = PlotTest()
P.linePlot()
P.fig.show()
Now I was thinking I could use P.fig.clear() any time to simply clear P.fig, but apparently that's not the case.
Writing P.fig.clear() directly into the script and execute it together it works and all I see is an empty figure. However that's rather pointless as I never get to see the actual plot like that.
Doing P.fig.clear() manually in the console does not do anything, regardless if the plot window is open or not, all other possible commands fail as well:
P.fig.clf()
P.axes[0].clear()
P.axes[0].cla()
mpp.clf()
mpp.cla()
mpp.close(P.fig)
Wrapping the command into a class method doesn't work either:
def clearFig(self):
self.fig.clear()
EDIT ================
After a clear() fig.axes is empty, yet show() still shows the old plot with the axes still being plotted.
/EDIT ================
Is it because I switched off interactive mode?
If you add a call to plt.draw() after P.fig.clear() it clears the figure. From the docs,
This is used in interactive mode to update a figure that has been altered, but not automatically re-drawn. This should be only rarely needed, but there may be ways to modify the state of a figure with out marking it as stale. Please report these cases as bugs.
I guess this is not a bug as you have switched off interactive mode so it is now your responsibility to explicitly redraw when you want to.
You can also use P.fig.canvas.draw_idle() which could be wrapper in the class as clearFigure method.

Acessing data inside a Matplotlib GUI callback function

I am new to Python, and somewhat new to object oriented programming. Can anyone explain what is going on and how things are typically done with a matplotlib GUI callback? I've taken the "event_handling example code" from the Matplotlib website and stripped it down for clarity. When you run this code it makes a plot, and if you press a key on the keyboard the press function is called. The press function is passed only event, but somehow every other variable from main program level appears inside the call to press but as a global variable, is this normal for functions? I can print the value of x, but if I try to change it then it makes a local variable version, worse yet now I have seemingly no way to access the global version anymore?
#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
x=np.random.rand(3)
y=np.random.rand(3)
def press(event):
print(x)
print('Local Var:', locals().keys())
print('Global Var:', globals().keys())
fig, ax = plt.subplots()
fig.canvas.mpl_connect('key_press_event', press)
ax.plot(x,y)
plt.show()
I have searched and had quite a hard time finding any reference that explains how to access or properly pass useful data in and out of the callback function so that a GUI event can do something useful, like update some data or feature of a plot?
So lets say I wanted to have the callback function modify y and re-plot the data. How is that typically done?
you have global access to x inside your callback, but can't modify it unless you specify it global.
def press(event):
global x
...
locals().keys() and globals().keys() are printing namespaces; I am unsure why you need to do that.
Your callback receives an event that you can use and manipulate inside the function.
Here is an example:
#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
x=np.random.rand(3)
y=np.random.rand(3)
def press(event):
print(event, event.key)
fig, ax = plt.subplots()
fig.canvas.mpl_connect('key_press_event', press)
ax.plot(x,y)
plt.show()
click on the plot window to set the focus.
pressing f should print the event object and set the plot full screen
pressing f again, will print f and restore the size of the window
pressing s will print s and will offer to save your figure
etc...
To learn more about how you can manipulate events, look up backend_bases in the very rich matplotlib web site. For example, you can set mouse_clicks events that allow you to capture canvas coordinates to add points or modify figures...

How to set timeout to pyplot.show() in matplotlib?

I am using python's matplotlib to draw figures.
I want to draw a figure with a timeout, say 3 seconds, and the window will close to move on the code.
I have known that pyplot.show() will create a blocking window with unlimited timeout; pyplot.show(block=False) or pyplot.draw() will make the window non-blocking. But what I want is let the code block for some seconds.
I have come up an idea that I might use an event handler or something, but still not really clear how to solve this. Is there any simple and elegant solution?
Suppose my code is like the following:
Draw.py:
import matplotlib.pyplot as plt
#Draw something
plt.show() #Block or not?
Here's a simple example where I have created a timer to set your timeout and performed closing of window plot.close() in the callback function of the timer. Start the timer before plot.show() and after three seconds timer invokes close_event() and then continues with the rest of the code.
import matplotlib.pyplot as plt
def close_event():
plt.close() #timer calls this function after 3 seconds and closes the window
fig = plt.figure()
timer = fig.canvas.new_timer(interval = 3000) #creating a timer object and setting an interval of 3000 milliseconds
timer.add_callback(close_event)
plt.plot([1,2,3,4])
plt.ylabel('some numbers')
timer.start()
plt.show()
print("I am doing something else")
Hope that was helpful.
This did not work for me on Mac OSX.
Looks like there are two issues:
Calling plt.close() is not enough to exit the
program sys.exit() works.
The scheduled function seems to be called at startup.
This results in a display time of zero. The window
pops and disappears at the same time. Using state in
the callback that treats the first call differently
solves this problem. An instance of a class with the
special method __call__() is a good approach for such
a stateful callable.
This works for me:
from __future__ import print_function
import sys
import matplotlib.pyplot as plt
class CloseEvent(object):
def __init__(self):
self.first = True
def __call__(self):
if self.first:
self.first = False
return
sys.exit(0)
fig = plt.figure()
timer = fig.canvas.new_timer(interval=3000)
timer.add_callback(CloseEvent())
plt.plot([1,2,3,4])
plt.ylabel('some numbers')
timer.start()
plt.show()
print("Am doing something else")

matplotlib: deliberately block code execution pending a GUI event

Is there some way that I can get matplotlib to block code execution pending a matplotlib.backend_bases.Event?
I've been working on some classes for interactively drawing lines and polygons inside matplotlib figures, following these examples. What I'd really like to do is block execution until I'm done editing my polygon, then get the final positions of the vertices - if you're familiar with MATLAB, I'm basically trying to replicate the position = wait(roihandle) syntax, for example here.
I suppose I could set some class attribute of my interactive polygon object when a keypress occurs, then repeatedly poll the object in my script to see if the event has occurred yet, but I was hoping there would be a nicer way.
Well, that was easier than I thought it would be! For those who are interested I found a solution using figure.canvas.start_event_loop() and figure.canvas.stop_event_loop().
Here's a simple example:
from matplotlib import pyplot as plt
class FigEventLoopDemo(object):
def __init__(self):
self.fig, self.ax = plt.subplots(1, 1, num='Event loop demo')
self.clickme = self.ax.text(0.5, 0.5, 'click me',
ha='center', va='center',
color='r', fontsize=20, picker=10)
# add a callback that triggers when the text is clicked
self.cid = self.fig.canvas.mpl_connect('pick_event', self.on_pick)
# start a blocking event loop
print("entering a blocking loop")
self.fig.canvas.start_event_loop(timeout=-1)
def on_pick(self, event):
if event.artist is self.clickme:
# exit the blocking event loop
self.fig.canvas.stop_event_loop()
print("now we're unblocked")

Categories