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?

2
  • Is Index() a custom function you're using? I'm trying to reproduce the issue, but keep getting a not defined error on Index(). Commented Dec 16, 2021 at 8:22
  • @JordanPaldino Hi Jordan, this line ("...Index()") should have been removed, sorry. Commented Dec 16, 2021 at 8:37

1 Answer 1

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()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.