Search code examples
pythonmatplotlibpygtkgtk3glade

Funcanimation not working inside figcanvas


I want to build an interface with certain real-time graphs showing the results of some experiments. For this, I decided to use a combination of glade(UI), gtk, python, and matplotlib. I was working with some basics and I was able to plot some real-time graphs.

Now, I have some trouble using Funcanimation for real-time animations. Below, the code import a glade file with four scrolled windows and I want to display some animation in each scrolled windows. I tired the animation without plotting inside the canvas (inside the scrolled window) and it works!. But when I tried to run this, the callback function by Funcanimation (update_line) is not even triggering. What is actually I'm doing wrong here. I am new to python as well.

Thanks

#!/usr/bin/env python
import sys
import os
import time
import psutil as p
import threading
import numpy as np
from gi.repository import Gtk
from gi.repository import GObject
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
import matplotlib.pyplot as plt




class windowSignals:
    def on_mainWindow_destroy(self, widget):
        Gtk.main_quit()

def main():
    builder = Gtk.Builder()
    builder.add_from_file("window.glade")
    builder.connect_signals(windowSignals())
    window = builder.get_object("mainWindow")
    sw = builder.get_object("scrolledWindow1")


    def update_line(num, data, line):
        data.pop(0)
        data.append(np.random.random())
        line.set_ydata(data)

        return line,


    fig1 = plt.figure()
    data = [0.0 for i in xrange(100)]
    l, = plt.plot(data, 'r-')

    plt.ylim(-1, 1)
    line_ani = animation.FuncAnimation(fig1, update_line, 25, fargs=(data, l), interval=50, blit=True)
    can = FigureCanvas(fig1)
    sw.add_with_viewport(can)
    can.draw()

    window.show_all()
    Gtk.main()

if __name__ == "__main__":
    main()

Solution

  • Many thanks to @ImportanceOfBeingErnest who deserves credit for basically everything that is correct in this post :)

    Here is a slightly modified, runnable version of your code (without Glade) which uses FuncAnimation inside a GTK app.

    Three things to note:

    • A reference to the FuncAnimation object must be kept lest the object (and its timer) be garbage collected.

    • The FigureCanvas should be created before the FuncAnimation, since FuncAnimation creates a timer by calling fig.canvas.new_timer(). If the canvas has not yet been created, fig.canvas is None and you get an AttributeError.

    • If Gtk is not your default backend, use matplotlib.figure.Figure instead of plt.figure here.


    import numpy as np
    from gi.repository import Gtk
    from gi.repository import GObject
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
    import matplotlib.pyplot as plt
    import matplotlib.animation as animation
    
    class MyApp(object):
        def __init__(self):
            window = Gtk.Window()
            window.connect("delete-event", Gtk.main_quit)
            window.set_default_size(400, 400)
            sw = Gtk.ScrolledWindow()
            window.add(sw)
    
            self.fig = fig = Figure()
            self.canvas = FigureCanvas(fig)
            sw.add_with_viewport(self.canvas)
    
            ax = fig.add_subplot(111)
            data = np.zeros(100)
            self.line, = ax.plot(data, 'r-')
            ax.set_ylim(-1, 1)
            self.ani = animation.FuncAnimation(
                self.fig, self.update_line, interval=100, frames=50, repeat=True)
    
            window.show_all()
    
        def update_line(self, *args):
            data = 2*(np.random.random(100)-0.5)
            self.line.set_ydata(data)
            self.canvas.draw()
            return True
    
    if __name__ == "__main__":
        app = MyApp()
        Gtk.main()