Matthias Verstraete
Matthias Verstraete

Reputation: 85

FuncAnimation only starts after window resize

I'm making an application that should visualize sensor data. Therefore I'm using python with tkinter as gui framework and matplotlib to visualize the data. The graph should show the current sensor value, so it should be animated. However, since I don't want to animate the graph when the sensor is not connected, I added a connect button, which needs to start the animation. This all works as expected, however, the animation only starts after I resize the tkinter window. I assume this triggers a redraw of all components, but I don't know how to trigger this from code.

I already tried calling the functions root.update() and root.update_idletasks() but that didn't help.

Here is a minimal example of the code:

import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.animation as animation
import matplotlib.pyplot as plt

from matplotlib import style


root = tk.Tk()
style.use('fivethirtyeight')

ys = [i for i in range(100)]

def animate(i, ax1, line):
    global ys
    ys = ys[1:] + [ys[0]]
    line.set_ydata(ys)

    # Draw x and y lists
    ax1.relim()
    ax1.autoscale_view(True,True,True)

    return line

class Gui():
    def __init__(self):
        global ys
        self._fig = plt.figure()
        self._ax1 = self._fig.add_subplot(1,1,1)

        self._line, = self._ax1.plot(ys)
        plt.xticks(ha='right')
        plt.subplots_adjust(bottom=0.30)
        plt.title('Air pressure measured')
        plt.ylabel('Sensor value')

        top_frame = tk.Frame(root, bg='cyan', width = 450, height=50)
        top_frame.pack(side=tk.LEFT, anchor=tk.N, pady=10)
        self.connectButton = tk.Button(top_frame, 
                                        text="connect",
                                        command=self.toggleAnimate)
        self.connectButton.pack()

        self._canvas = FigureCanvasTkAgg(self._fig, master=root)
        self._canvas.draw()
        self._canvas.get_tk_widget().pack()

    def start(self):
        root.mainloop()

    def toggleAnimate(self):
        self._animate = animation.FuncAnimation(self._fig, animate, fargs=(self._ax1, self._line), interval=100, blit=False)
        root.update_idletasks()
        root.update()

if __name__ == "__main__":
    g = Gui()
    g.start()

Upvotes: 2

Views: 454

Answers (1)

Reblochon Masque
Reblochon Masque

Reputation: 36662

You need to call draw_idle once on the FigureCanvasTkAgg to get things rolling.

I placed root inside the GUI, and removed the calls to update and update_idletasks that were not necessary, and might have interfered with the mainloop.
I also packed your canvas inside the frame, but you can relocate it to root if you prefer.

import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.animation as animation
import matplotlib.pyplot as plt

from matplotlib import style


style.use('fivethirtyeight')

ys = [i for i in range(100)]

def animate(i, ax1, line):
    global ys
    ys = ys[1:] + [ys[0]]
    line.set_ydata(ys)

    # Draw x and y lists
    ax1.relim()
    ax1.autoscale_view(True,True,True)

    return line

class Gui():
    def __init__(self):
        self.root = tk.Tk()
        self._fig = plt.figure()
        self._ax1 = self._fig.add_subplot(1,1,1)

        self._line, = self._ax1.plot(ys)
        plt.xticks(ha='right')
        plt.subplots_adjust(bottom=0.30)
        plt.title('Air pressure measured')
        plt.ylabel('Sensor value')

        top_frame = tk.Frame(self.root, bg='cyan', width = 450, height=50)
        top_frame.pack(side=tk.LEFT, anchor=tk.N, pady=10)
        self.connectButton = tk.Button(top_frame, 
                                        text="connect",
                                        command=self.toggleAnimate)
        self.connectButton.pack()

        self._canvas = FigureCanvasTkAgg(self._fig, master=top_frame) #master=self.root)
        self._canvas.get_tk_widget().pack(expand=True, side=tk.LEFT, anchor=tk.N, pady=10, padx=10)
        self._canvas.draw()        
        self._animate = None

    def start(self):
        self.root.mainloop()

    def toggleAnimate(self):
        if self._animate is None:
            self._canvas.draw_idle()
            self._animate = animation.FuncAnimation(self._fig, animate, fargs=(self._ax1, self._line), interval=100) #, blit=False)


if __name__ == "__main__":
    g = Gui()
    g.start()

Upvotes: 2

Related Questions