Python: Add optional argument into matplotlib button on_clicked function - python

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

Related

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, 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()

Python Matplotlib callback function with parameters

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()

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:

Matplotlib scroll bar

I have a line generalisation algorithm and want to add a scroll bar to the plot that will increase the tolerance (i,e make the line more and more generalised). Using matplotlib how would this be possible?
So to sum up, I want to be able to click and drag a slider that will display the increase in the tolerances effect on the line.
Still really struggling with this. I only want one slider on a simple scale from 1-10.
yeah the demo helps, i'm just struggerling to get one slider to work, this is what I have so far,
fig = mp.figure()
ax = fig.add_subplot(111)
fig.subplots_adjust(left=0.25, bottom=0.25)
min0=1
max0=10
tolerance = 0
chain1 = ChainLoader('Wiggle1.txt')
chain = chain1[0]
chain2 = chain.generalise(tolerance)
axcolor = 'lightgoldenrodyellow'
axmin = fig.add_axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
axmax = fig.add_axes([0.25, 0.15, 0.65, 0.03], axisbg=axcolor)
tolerance = Slider(axmin, 'Min', 1, 10, valinit=min0)
#smax = Slider(axmax, 'Max', 0, 30000, valinit=max0)
def update(val):
tolerance = tolerance.val
#pp.show()
tolerance.on_changed(update)
#smax.on_changed(update)
chain2 = chain.generalise(tolerance)
pp.plotPolylines(chain2)
pp.show()
My problems are how to write the def update section. Any help?
from PointPlotter import PointPlotter
from ChainHandler import ChainLoader
pp=PointPlotter()
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
ax = plt.subplot(111)
plt.subplots_adjust(left=0.25, bottom=0.25)
tolerance = 0
f0 = 0
chain2 = ChainLoader('Wiggle1.txt')
for chain in chain2:
chain2 = chain.generalise(tolerance)
pp.plotPolylines(chain2)
axcolor = 'lightgoldenrodyellow'
axtol = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
tolerance = Slider(axtol, 'tol', 0.1, 30.0, valinit=f0)
def update(val):
tolerance = tolerance.val
for chain in chain2:
chain2 = chain.generalise(tolerance)
pp.plotPolylines(chain2)
pp.plotPolylines(chain2)
tolerance.on_changed(update)
plt.show()
So close! Its now plotting, but returns "UnboundLocalError: local variable 'tolerance' referenced before assignment" when the scroll bar is used. #tcaswell any help?
You want the slider widget (doc).
Here is the demo from the examples:
http://matplotlib.org/examples/widgets/slider_demo.html
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
ax = plt.subplot(111)
plt.subplots_adjust(left=0.25, bottom=0.25)
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])
axcolor = 'lightgoldenrodyellow'
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
axamp  = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=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))
    plt.draw()
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)
rax = plt.axes([0.025, 0.5, 0.15, 0.15], facecolor=axcolor)
radio = RadioButtons(rax, ('red', 'blue', 'green'), active=0)
def colorfunc(label):
    l.set_color(label)
    plt.draw()
radio.on_clicked(colorfunc)
plt.show()
To adapt this to your case:
#smax.on_changed(update)
chain2 = chain.generalise(tol)
pp.plotPolylines(chain2)
def update(val):
tol = tolerance.val # get the value from the slider
chain2 = chain.generalise(tol) # shove that value into your code
ax.cla() # clear the axes
pp.plotPolylines(chain2) # re-plot your new results
# register the call back
tolerance.on_changed(update)
Be careful about re-using variable names (you use tolerance twice, once for a float and once for the Slider and python will happily clobber your old variables with new ones of an entirely different type).
In update I went with the most brute-force approach, clearing the axes and then re-drawing it, in general you want to grab the artists that are returned by plotPolylines and update those with your new data. (If you need help with that step, open a new question with details about your data structure).
The way to understand .on_changed is that when the slider notices it has been changed, it will call the function you passed in (update) with a single argument (val) which is the current value of the slider. Inside that function you can do what ever you want, and it will be executed in full every time the slider is changed.

Categories