0

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?

OnY
  • 897
  • 6
  • 12
  • Is Index() a custom function you're using? I'm trying to reproduce the issue, but keep getting a not defined error on Index(). – Jordan Paldino Dec 16 '21 at 08:22
  • @JordanPaldino Hi Jordan, this line ("...Index()") should have been removed, sorry. – OnY Dec 16 '21 at 08:37

1 Answers1

1

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()
Jonathan Weine
  • 655
  • 3
  • 11