Python Matplotlib callback function with parameters - python

In a callback function of button presses, is there anyway to pass more parameters other than 'event'? For example, in the call back function, I want to know the text of the button ('Next' in this case). How can I do that?
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
fig = plt.figure()
def next(event):
# I want to print the text label of the button here, which is 'Next'
pass
axnext = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(next)
plt.show()

Another possibly quicker solution is to use a lambda function:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
fig = plt.figure()
def next(event, text):
print(text)
pass
axnext = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(lambda x: next(x, bnext.label.get_text()))
plt.show()

To obtain that, you might need to encapsulate event processing in a class, as in official tutorial:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class ButtonClickProcessor(object):
def __init__(self, axes, label):
self.button = Button(axes, label)
self.button.on_clicked(self.process)
def process(self, event):
print self.button.label
fig = plt.figure()
axnext = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = ButtonClickProcessor(axnext, "Next")
plt.show()

Related

Python: Add optional argument into matplotlib button on_clicked function

I made some function of that kind:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
def clicked(event):
print("Button pressed")
button_pos = plt.axes([0.2, 0.9, 0.1, 0.075])
b1 = Button(button_pos, 'Button1')
b1.on_clicked(clicked)
button_pos = plt.axes([0.2, 0.8, 0.1, 0.075])
b2 = Button(button_pos, 'Button2')
b2.on_clicked(clicked)
plt.show()
My aim now, is to add an second argument into the clicked function. The function now has the following form:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
def clicked(event, text):
print("Button pressed"+text)
button_pos = plt.axes([0.2, 0.9, 0.1, 0.075])
b1 = Button(button_pos, 'Button1')
b1.on_clicked(clicked(text=" its the first"))
button_pos = plt.axes([0.2, 0.8, 0.1, 0.075])
b2 = Button(button_pos, 'Button2')
b2.on_clicked(clicked)
b2.on_clicked(clicked(text=" its the second"))
plt.show()
But with that change I get the following error message:
Traceback (most recent call last):
File "/bla/main.py", line 24, in <module>
b1.on_clicked(clicked(text=" its the first"))
TypeError: clicked() missing 1 required positional argument: 'event'
Is their a way to put an second argument in such a function or is it required in Python to make two on_clicked functions in that case?
the problem with your second code is that you are calling the function clicked when you use it inside b1.on_clicked. This raises the error.
Instead b1.on_clicked takes a function as an argument and then under the hood it calls that function, passing the event as a parameter.
you can do it like this
def fn_maker(text=''):
def clicked(event):
print(f"Button pressed{text}")
return clicked
button_pos = plt.axes([0.2, 0.9, 0.1, 0.075])
b1 = Button(button_pos, 'Button1')
b1.on_clicked(fn_maker(text=" its the first"))
...

Pyplot grid is not shown if I add Button widget

I have the following code. The grid is visible without the Button widget. But when the grid is not shown if I add the button. What am I doing wrong?
from matplotlib import pyplot as plot
from matplotlib.widgets import Button
plot.plot([1,2,3], [1,2,3])
ax = plot.axes([0.5, 0.5, 0.05, 0.05])
Button(ax, "A")
plot.grid()
plot.show()
For me you code is working fine! (Except the button is in the middle of the screen)
Maybe you should try the following snippet:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
freqs = np.arange(2, 20, 3)
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.2)
t = np.arange(0.0, 1.0, 0.001)
s = np.sin(2*np.pi*freqs[0]*t)
l, = plt.plot(t, s, lw=2)
class Index:
ind = 0
def next(self, event):
self.ind += 1
i = self.ind % len(freqs)
ydata = np.sin(2*np.pi*freqs[i]*t)
l.set_ydata(ydata)
plt.draw()
def prev(self, event):
self.ind -= 1
i = self.ind % len(freqs)
ydata = np.sin(2*np.pi*freqs[i]*t)
l.set_ydata(ydata)
plt.draw()
callback = Index()
axprev = plt.axes([0.7, 0.05, 0.1, 0.075])
axnext = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(callback.prev)
plt.show()
To read more about matplotlib buttons read and see the snippet: https://matplotlib.org/stable/gallery/widgets/buttons.html
Also you might want to rename the button to have a bigger name like "TEST" to avoid problems regarding the size of the label.
The Button() instantiation changes the current axes. So when I call plot.grid() it is operated on Button axes. I changed the order of calling plot.grid() and it worked. I have shown the modified code below.
from matplotlib import pyplot as plot
from matplotlib.widgets import Button
plot.plot([1,2,3], [1,2,3])
# note grid() is called before Button
plot.grid()
ax = plot.axes([0.5, 0.5, 0.05, 0.05])
Button(ax, "A", hovercolor="red")
plot.show()

matplotlib FuncAnimation won't start on button press widget event

matplotlib noob here.
I am trying to create an animation (which starts on a button click) of a normal distribution being populated, where the distribution parameters (mean and std dev) are selected using two slider widgets.
Please help. I have pasted my code below
%matplotlib notebook
from pdb import set_trace as bp
import matplotlib.animation as animation
import numpy as np
from matplotlib.widgets import Slider, Button, RadioButtons
fig = plt.figure()
n = 1000
x = np.array([])
bins = np.arange(-4, 4, 0.5)
plt.hist(x, bins=bins)
plt.subplots_adjust(bottom=0.25)
def update(curr):
if curr == n:
a.event_source.stop()
plt.cla()
plt.hist(x[:curr], bins=bins)
plt.axis([-4,4,0,500])
plt.gca().set_title('Sampling the Normal Distribution')
plt.gca().set_ylabel('Frequency')
plt.gca().set_xlabel('Value')
plt.annotate('n = {}'.format(curr), [3,480])
axcolor = 'lightgoldenrodyellow'
axmu = plt.axes([0.15, 0.1, 0.65, 0.03], facecolor=axcolor)
axstdev = plt.axes([0.15, 0.15, 0.65, 0.03], facecolor=axcolor)
muslider = Slider(axmu, 'mean', 0.1, 30.0, valinit=0)
stdevslider = Slider(axstdev, 'stdev', 0.1, 10.0, valinit=1.0)
startax = plt.axes([0.4, 0.025, 0.1, 0.04])
startbutton = Button(startax, 'Start', color=axcolor, hovercolor='0.975')
newmean = 0
newstdev = 1.0
def getnewparams(val):
global newmean
global newstdev
newmean = muslider.val
newstdev = stdevslider.val
def startanimation(event):
print(f"params now is {newmean} {newstdev}")
global x
x = np.random.normal(loc=newmean, scale=newstdev, size=n)
a = animation.FuncAnimation(fig, update, interval=100)
a.event_source.start()
muslider.on_changed(getnewparams)
stdevslider.on_changed(getnewparams)
startbutton.on_clicked(startanimation)
How my plot looks now
Using #jasonharper's suggestion, I was able to resolve the issue on my own. I am pasting the working code below
%matplotlib notebook
import matplotlib.pyplot as plt
from pdb import set_trace as bp
import matplotlib.animation as animation
import numpy as np
from matplotlib.widgets import Slider, Button, RadioButtons
fig = plt.figure()
n = 1000
a = None
x = np.array([])
# bins = np.arange(-4, 4, 0.5)
plt.hist(x, bins=bins)
plt.subplots_adjust(bottom=0.25)
histaxis = plt.gca()
def update(curr):
if curr == n:
a.event_source.stop()
histaxis.cla()
histaxis.hist(x[:curr], bins=100)
# histaxis.axis([-4,4,0,500])
histaxis.set_title('Sampling the Normal Distribution')
histaxis.set_ylabel('Frequency')
histaxis.set_xlabel('Value')
histaxis.annotate('n = {}'.format(curr), [3,480])
axcolor = 'lightgoldenrodyellow'
axmu = plt.axes([0.15, 0.1, 0.65, 0.03], facecolor=axcolor)
axstdev = plt.axes([0.15, 0.15, 0.65, 0.03], facecolor=axcolor)
muslider = Slider(axmu, 'mean', 0.1, 30.0, valinit=0)
stdevslider = Slider(axstdev, 'stdev', 0.1, 10.0, valinit=1.0)
startax = plt.axes([0.4, 0.025, 0.1, 0.04])
startbutton = Button(startax, 'Start', color=axcolor, hovercolor='0.975')
newmean = 0
newstdev = 1.0
def getnewparams(val):
global newmean
global newstdev
newmean = muslider.val
newstdev = stdevslider.val
def startanimation(event):
print(f"params now is {newmean} {newstdev}")
global x
x = np.random.normal(loc=newmean, scale=newstdev, size=n)
global a
a = animation.FuncAnimation(fig, update, interval=100)
a.event_source.start()
muslider.on_changed(getnewparams)
stdevslider.on_changed(getnewparams)
startbutton.on_clicked(startanimation)

matplotlib, plt.show() in a different method = no on_clicked

When I put plt.show() in a different method, it's impossible to click the button :
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class ButtonTest:
def __init__(self):
ax = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(ax, 'Next')
bnext.on_clicked(self._next)
# plt.show()
def show(self):
print("when i put plt.show() in a different method, it's impossible to click the button")
plt.show()
def _next(self, event):
print("next !")
b = ButtonTest()
b.show()
The button is not even highlighted when the mouse moves over it. Would someone know why and how to solve the problem ?
What's happening is that the button object is being garbage collected before the plot is displayed. You'll need to keep a reference to it around.
For example, if you change
bnext = Button(...)
to
self.bnext = Button(...)
Everything should work.
As a complete example:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class ButtonTest:
def __init__(self):
ax = plt.axes([0.81, 0.05, 0.1, 0.075])
self.bnext = Button(ax, 'Next')
self.bnext.on_clicked(self._next)
def show(self):
plt.show()
def _next(self, event):
print("next !")
ButtonTest().show()

Interactive matplotlib plots via textboxes

I'm trying to create an interactive matplotlib plot of a multidimensional function with three parameters to vary. The problem is that the parameters can vary over a very large range, so I'd rather not use sliders but directly type the value I'd like. Basically, I'd like to recreate the canonical example below where instead of sliders I'd like text boxes in which I can input parameters
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.35)
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
l, = plt.plot(t,s, lw=2, color='red')
plt.axis([0, 1, -10, 10])
ax.plot(t,t)
axcolor = 'lightgoldenrodyellow'
axamp = plt.axes([0.25, 0.25, 0.65, 0.03], axisbg=axcolor)
axfreq = plt.axes([0.25, 0.2, 0.65, 0.03], axisbg=axcolor)
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
def update(val):
amp = samp.val
freq = sfreq.val
l.set_ydata(amp*np.sin(2*np.pi*freq*t))
fig.canvas.draw_idle()
sfreq.on_changed(update)
samp.on_changed(update)
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
def reset(event):
sfreq.reset()
samp.reset()
button.on_clicked(reset)
plt.show()
You can add a GUI panel to the figure window. Here is an example to use the Qt4Agg backend, and add a QDockWidget to the main figure window, and then you can add QWidgets to the dock window.
import numpy as np
import matplotlib
matplotlib.use("Qt4Agg") # This program works with Qt only
import pylab as pl
fig, ax1 = pl.subplots()
t = np.linspace(0, 10, 200)
line, = ax1.plot(t, np.sin(t))
### control panel ###
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import Qt
def update():
freq = float(textbox.text())
y = np.sin(2*np.pi*freq*t)
line.set_data(t, y)
fig.canvas.draw_idle()
root = fig.canvas.manager.window
panel = QtGui.QWidget()
hbox = QtGui.QHBoxLayout(panel)
textbox = QtGui.QLineEdit(parent = panel)
textbox.textChanged.connect(update)
hbox.addWidget(textbox)
panel.setLayout(hbox)
dock = QtGui.QDockWidget("control", root)
root.addDockWidget(Qt.BottomDockWidgetArea, dock)
dock.setWidget(panel)
######################
pl.show()
Here is the screen:

Categories