Search code examples
matplotlibanimationtkinternumerical-methods

matplotlib animation embedded in tkinter : update function never called


I've been trying to embed a matplotlib animation into tkinter. The goal of this app is to simulate some differentials equations with rk4 method and show a real time graph as the simulation goes.

In fact the plot is rightly embedded into the tkinter frame. However, the animation never run, I've noticed that the update function is never called.

I've been searching everywhere but I didn't find anything.

Thanks for the help.

Here is a code sample of the GUI class showing where I execute the animation

  # called when I click on a button "start simulation"
  def plot_neutrons_flow(self):

    # getting parameters from the graphical interface 
    if not self._started: 
      I0 = float(self._field_I0.get())
      X0 = float(self._field_X0.get())
      flow0 = float(self._field_flow0.get())
      time_interval = float(self._field_time_interval.get())
      stop = int(self._field_stop.get())

      FLOW_CI = [I0, X0, flow0] # [I(T_0), X(T_0), PHI[T_0]]

      self._simulation = NeutronsFlow(
        edo=neutrons_flow_edo, 
        t0=0,
        ci=FLOW_CI,
        time_interval=time_interval,
        stop=hour_to_seconds(stop)
      )

      # launch the animation
      self._neutrons_flow_plot.animate(self._simulation)
      self._started = True

Here is the code for the matplotlib animation :

import matplotlib
import tkinter as tk
import matplotlib.pyplot as plt
import matplotlib.animation as animation

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 
from matplotlib.figure import Figure
from matplotlib import style

matplotlib.use("TkAgg")
style.use('seaborn-whitegrid')

class PlotAnimation(FigureCanvasTkAgg):
  def __init__(self, tk_root):
    self._figure = Figure(dpi=100)
    # bind plot to tkinter frame
    super().__init__(self._figure, tk_root)

    x_label = "Temps (h)"
    y_label = "Flux / Abondance"

    self._axes = self._figure.add_subplot(111, xlabel=x_label, ylabel=y_label, yscale="log")
    self.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)


  def update(self, interval):
    # this is never called

    # get data from rk4 simulation
    time_set = self._simulation.get_time_set()
    y_set = self._simulation.get_y_set()


    self._axes.clear()
    self._axes.plot(time_set, y_set, visible=True, linewidth=1)
    self._axes.legend(fancybox=True)

    # redraw canvas
    self.draw_idle()

  def animate(self, simulation):
    # this is called

    self._simulation = simulation
    # simulate differential equations with rk4 method
    self._simulation.resolve()

    # https://github.com/matplotlib/matplotlib/issues/1656
    anim = animation.FuncAnimation(
      self._figure, 
      self.update,
      interval=1000
    )

EDIT :

The solution was to instantiate the FuncAnimation function directly in the init method


Solution

  • As indicated in the documentation of the animation module (emphasis mine)

    (...) it is critical to keep a reference to the instance object. The animation is advanced by a timer (typically from the host GUI framework) which the Animation object holds the only reference to. If you do not hold a reference to the Animation object, it (and hence the timers), will be garbage collected which will stop the animation.

    You need to return the anim object from your animate() function, and store it somewhere in your code so that it is not garbage-collected