I have a code that uses matplotlib and the Button widget. It all works well, but when this code is written as a function, the buttons stop working. This is because after the function runs, the button objects are being removed by the garbage collector.
Here is an example of the code that does not work well:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
def fun():
def Prev(x):
print("Prev")
def Next(x):
print("Next")
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)
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(Next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(Prev)
fun()
Here is my non-so-elegant solution:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
def fun():
def Prev(x):
print("Prev")
def Next(x):
print("Next")
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)
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(Next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(Prev)
return bnext,bprev
b1,b2=fun()
Is there a better, best-practice solution for this kind of problem?
As you correctly assumed the key is to keep alive a reference to the button in the outer scope. If you don't want to return a collection of buttons what you could do is, to add the buttons as attribute of the figure. I wouldn't say there is a best practice for such a thing of just plotting one figure, and for a more complex UI there might be better solutions. But this code solves this specific issue without having to explicitly returning the button references.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import matplotlib
matplotlib.use("TkAgg")
def fun():
def Prev(x):
print("Prev")
def Next(x):
print("Next")
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)
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(Next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(Prev)
fig.bnext = bnext # add button references to figure object
fig.bprev = bprev
if __name__ == "__main__":
fun()
plt.show()