Acessing data inside a Matplotlib GUI callback function - python

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...

Related

jupyter notebook - matplotlib shows figure even without calling plt.show()

The following is a simplified example of my code. The idea behind this class is to show the figure only when the show method is executed.
# my_module.py
import matplotlib.pyplot as plt
import numpy as np
class Test:
def __init__(self):
self._fig = plt.figure()
self.ax = self._fig.add_subplot(1, 1, 1)
def show(self):
x = np.linspace(0, 10, 100)
y = np.sin(x)
self.ax.plot(x, y)
self._fig.tight_layout()
self._fig.show()
The code works as expected when it is executed from a Python shell or ipython. However, if I run this inside a Jypter Notebook:
from my_module import Test
t = Test()
At this point, an empty figure is visualized on the screen. I don't want that! Now, I tried to insert plt.close(self._fig) inside __init__, but then when I run t.show() I get UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure.
I also tried to load %matplotlib widget with the previous edit plt.close(self._fig). The pictures is only shown when show is called, but it is just a picture with no interactive frame.
Another option would be to rewrite the class in such a way that the figure is created inside the show method. This is far from optimal as I would need to re-adjust my tests.
Are there any other ways to get it working correctly on all shells?
In the original post I've done two mistakes.
First, the figure was instantiated into the __init__ method, then the show method was called. In an interactive environment, once the figure is created it will be shown on the screen. We could turn off that behaviour with plt.ioff(), but then two things can happen:
If %matplotlib widget was executed, the figure will show up only once when calling t.show().
Otherwise, no plot will be shown on the screen when calling t.show().
Hence, plt.ioff() is not a valid solution. Instead, the figure must be instantiated when t.show() is executed.
The second mistake I did was to use self._fig.show(). Remember, in a interactive environment the figure is shown as soon as it is instantiated. Then, the previous command shows the figure a second time! Instead, I have to use plt.show(), which only display the figure once.
Here is the correct code example:
import matplotlib.pyplot as plt
import numpy as np
class Test:
def __init__(self):
# init some attributes
pass
def show(self):
self._fig = plt.figure()
self.ax = self._fig.add_subplot(1, 1, 1)
x = np.linspace(0, 10, 100)
y = np.sin(x)
self.ax.plot(x, y)
self._fig.tight_layout()
plt.show()
t = Test() # no figure is shown
t.show() # figure is shown

Is there a way to plot something iteratively so that the new plot overwrites the previous plot?

What I want to do is to dynamically change a plot so that I can see it update as Python is executing its code. Here is what I've come up with:
import matplotlib.pyplot as plt
import time
def plotResult(x,y):
plt.plot(x,y)
plt.figure()
for i in range(5):
x = [2,3,5*i]
y = [1,2,3]
plotResult(x,y)
time.sleep(1)
What I want is for each call of "plotResult" to erase the previous plot with the new plot in its place. What I end up with instead is each plot on top of each other. I'm using time.sleep here because I want some time to look at the newly plotted result before it gets erased and replaced with a new plot. I guess I'm essentially trying to create an animation here with each frame being a call to plotResult.
I'm going to do this for a code with a much longer execution time, so I don't want to have to wait until the code is done being executed to view the animation. Please let me know if you know of a way to do this.
Read about the FuncAnimation class. It repeatedly calls a function to update each frame of the animation.

Understanding matplotlib event handling: what are event and mpl_connect?

I wanted to make it possible to show values when pressing a dot in my scatterplot. The solution was found here: Possible to make labels appear when hovering over a point in matplotlib?
Solution:
from matplotlib.pyplot import figure, show
import numpy as npy
from numpy.random import rand
# picking on a scatter plot (matplotlib.collections.RegularPolyCollection)
x, y, c, s = rand(4, 100)
def onpick3(event):
ind = event.ind
print 'onpick3 scatter:', ind, npy.take(x, ind), npy.take(y, ind)
fig = figure()
ax1 = fig.add_subplot(111)
col = ax1.scatter(x, y, 100*s, c, picker=True)
#fig.savefig('pscoll.eps')
fig.canvas.mpl_connect('pick_event', onpick3)
show()
And it solved my problem. But I don't understand how, I've been googling around without any luck. I know how to plot with matplotlib, so that's not where my knowledge is lacking.
One thing I don't understand is the onpick3(event) function. What is this event parameter? Because the function itself is called upon further down without any given arguments: fig.canvas.mpl_connect('pick_event', onpick).
mpl_connect connects a signal to a slot. The slot is in this case onpick3.
Note that the slot is not called, i.e. the syntax is fig.canvas.mpl_connect('pick_event', onpick3) and not fig.canvas.mpl_connect('pick_event', onpick3())
It will only be called once the signal is triggered (mouse clicked on canvas). At this point the underlying event is provided as an argument in the function call.
You'll see that once you try to define the slot without argument. This would cause an error like onpick3 expects 0 arguments but got 1 or so.
You'll find details on the matplotlib event handling page.
The event itself is an instance of matplotlib.backend_bases.PickEvent. The .ind attribute is not well documented, but that is mainly because not all artists actually register this attribute to the event.

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.

Plot in python that updates on keyboard hit

I've been struggling to achieve something that is trivial in Octave: to produce a series of plots, that change when I hit a key. Here's my octave code sample.
x = [1:10];
for c=1:3
plot(x,c.*x);
hold off;
input('a');
end
When I try to do the same in python, I realized that python matplotlib has the save function, which puts it in non-blocking mode, and so I have to close the figure using a mouse, for the next figure to be produced. And the next figure is at a random other location on the screen. How can I get python to imitate the above behavior? I've tried various combinations of ion(), ioff(), plt.show(), plt.draw(), but haven't succeeded.
You can do something fancier if you want, using mpl_connect
First import pylab
from pylab import *
Then define an updateData function that can be connected to the figure canvas.
i = 1
def updateData(event):
global i,x
i +=1
y = i*x
data.set_data(x,y)
ylim(y[0],y[-1])
draw()
i and x are global variables in this case. (This could be treated in better way, this is just an example!)
Then, create you plot and connect with your defined function.
f = figure()
data, = plot(x,x)
i=1
f.canvas.mpl_connect("key_press_event",updateData)
show()
Whenever you hit any key in the keyboard (When the figure window is selected) the function updateData is called, i is incremented and the plot updated.
Have fun!

Categories