1

I'm trying to use a horizontal slider to change the xlim of my plot. But first, I can't figure out how to get the slider to update using the on_changed() method. I don't have a strong understanding of how classes, and objects interact with each other.

I'm using this slider example as a template:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons

fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
delta_f = 5.0
s = a0 * np.sin(2 * np.pi * f0 * t)
l, = plt.plot(t, s, lw=2)
ax.margins(x=0)

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, valstep=delta_f)
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)

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)
    fig.canvas.draw_idle()
radio.on_clicked(colorfunc)

plt.show()

The part that I'm having trouble implementing in my app is:

sfreq.on_changed(update)
samp.on_changed(update)

It works fine if you're opening a plot using plt.show(), but if you're packing it into a canvas like I'm doing below, it stops working. Any ideas why?

Here is the code for my app: UPDATE: I simplified the code to focus on the problem.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import(FigureCanvasTkAgg, NavigationToolbar2Tk)

from tkinter import *
import tkinter as tk

global running
running = True

global graph_exists
graph_exists = False

class MyApp:
    def __init__(self, parent):
        self.myParent = parent ###remember my parent, the root
        self.sfreq = 0
        self.samp = 0

        
        
    def run(self):
        self.fig, self.ax = plt.subplots()
        plt.subplots_adjust(left=0.25, bottom=0.25)
        self.t = np.arange(0.0, 1.0, 0.001)
        a0 = 5
        f0 = 3
        delta_f = 5.0
        s = a0 * np.sin(2 * np.pi * f0 * self.t)
        self.l, = plt.plot(self.t, s, lw=2)
        self.ax.margins(x=0)

        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)

        self.sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0, valstep=delta_f)
        self.samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)

        canvas = FigureCanvasTkAgg(self.fig, root)
        canvas.draw()
        canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        graph_exists = True

        

    def update(self, val):
        amp = self.samp.val
        freq = self.sfreq.val
        self.l.set_ydata(amp*np.sin(2*np.pi*freq*self.t))
        self.fig.canvas.draw_idle()

    if graph_exists:
        self.sfreq.on_changed(update)
        self.samp.on_changed(update)
        

#Run the event loop
root = tk.Tk()
myapp = MyApp(root)
myapp.run()
root.mainloop()
krose
  • 390
  • 1
  • 6
  • 18
  • Missing prefix `self.` on `sfreq` and `samp`. – acw1668 Nov 19 '20 at 23:24
  • Thanks. Didn't fix the problem, but one step closer. I updated the question to reflect this. – krose Nov 20 '20 at 02:49
  • You called `self.main_graph.update_on_change()` inside `MyApp.__init__()`, but at that time, `self.sfreq` and `self.samp` are not created yet. Try removing that line. – acw1668 Nov 20 '20 at 03:05
  • ah. I see what you mean. So that eliminated the error, but that's because the slider just doesnt' do anything now. How can I tell it to check if graph_exists, and then list for on_changed()? – krose Nov 20 '20 at 03:28
  • 1
    You can call the two `.on_changed(self.update)` after creating the two `Slider`. Also you need to define `update()` like `def update(self, val)` instead. However, according to the [answer](https://stackoverflow.com/a/40330987/5317403) to other question, you need to add the figure into canvas before plotting. – acw1668 Nov 20 '20 at 06:15
  • After playing around with the code more, it seems it's an issue with canvas. I dont think on_changed is being added to the mainloop event list when changed. I'm going to simplify the question and maybe that'll help find an answer. – krose Nov 24 '20 at 18:53

2 Answers2

3

As said in the link in my comment, you need to add the figure to canvas before plotting, otherwise the Sliders don't work. Also you should call on_changed(...) after creating the Sliders.

    def run(self):
        self.fig, self.ax = plt.subplots()
        canvas = FigureCanvasTkAgg(self.fig, root)  # add the figure to canvas before plotting
        plt.subplots_adjust(left=0.25, bottom=0.25)
        self.t = np.arange(0.0, 1.0, 0.001)
        a0 = 5
        f0 = 3
        delta_f = 5.0
        s = a0 * np.sin(2 * np.pi * f0 * self.t)
        self.l, = plt.plot(self.t, s, lw=2)
        self.ax.margins(x=0)

        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)

        self.sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0, valstep=delta_f)
        self.samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
        ### setup on_changed callbacks
        self.sfreq.on_changed(self.update) # use 'self.update' instead of 'update'
        self.samp.on_changed(self.update)
        ###
        #canvas = FigureCanvasTkAgg(self.fig, root)
        canvas.draw()
        canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        graph_exists = True
acw1668
  • 40,144
  • 5
  • 22
  • 34
2

The problem with your code is the method update is never called... You need to put the part if graph_exists into the run method. Besides, as suggested in the post @acw1668 shared, you also need to add the figure into canvas before plotting.

Try the code below

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import(FigureCanvasTkAgg, NavigationToolbar2Tk)

from tkinter import *
import tkinter as tk

global running
running = True

global graph_exists
graph_exists = False

class MyApp:

    def __init__(self, parent):
        self.myParent = parent ###remember my parent, the root
        self.sfreq = 0
        self.samp = 0

    def run(self):
        self.fig, self.ax = plt.subplots()
        canvas = FigureCanvasTkAgg(self.fig, root)
        canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        
        plt.subplots_adjust(left=0.25, bottom=0.25)
        self.t = np.arange(0.0, 1.0, 0.001)
        a0 = 5
        f0 = 3
        delta_f = 5.0
        s = a0 * np.sin(2 * np.pi * f0 * self.t)
        self.l, = plt.plot(self.t, s, lw=2)
        self.ax.margins(x=0)

        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)

        self.sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0, valstep=delta_f)
        self.samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
        graph_exists = True
        
        if graph_exists:
            self.sfreq.on_changed(self.update)
            self.samp.on_changed(self.update)

    def update(self, val):
        print('inside update')
        amp = self.samp.val
        freq = self.sfreq.val
        self.l.set_ydata(amp*np.sin(2*np.pi*freq*self.t))
        
        self.fig.canvas.draw_idle()
        

#Run the event loop
root = tk.Tk()
myapp = MyApp(root)
myapp.run()
root.mainloop()
meTchaikovsky
  • 7,478
  • 2
  • 15
  • 34