Python Forum
[Tkinter] Tkinter Matplotlib Animation Graph not rendering
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Tkinter Matplotlib Animation Graph not rendering
#1
I am trying to plot a real time stock price graph on a tkinter frame using matplotlib's funcanimation and tkcanvas, however whenever I try and put it in the context of my project and use OOP/Classes, I get the following User Warning:

UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g. anim, that exists until you output the Animation using plt.show() or anim.save().
warnings.warn(

Here is my code (I am fairy new to OOP in general so there may be some mistakes or better ways of doing things):

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import pandas as pd
import tkinter
from matplotlib.backends.backend_tkagg import (
    FigureCanvasTkAgg, NavigationToolbar2Tk)

root = tkinter.Tk()

class graph:

    def __init__(self):
        self.graph_frame = tkinter.Frame(master=root, width=500, height=300)

    def price_graph(self):

        fig = plt.Figure(dpi=70, constrained_layout=True)
        ax = fig.add_subplot(111)
        plt.rcParams.update({'font.size':10})

        def animate(i):

            df = pd.read_csv('data.csv', skiprows=1)
            list_of_rows = [list(row) for row in df.values]
                
            xval = []
            yval = []
                
            for eachValue in list_of_rows:
                x,y = eachValue[0],eachValue[1]
                xval.append(int(x))
                yval.append(float(y))
            
            ax.clear()
            ax.plot(xval, yval, lw=1)
            
            return [ax]
        
        canvas = FigureCanvasTkAgg(fig, master=self.graph_frame)
        canvas.draw()
        canvas.get_tk_widget().pack()
        canvas._tkcanvas.pack()

        anim = FuncAnimation(fig, animate, interval=1000, blit=False)

stock_graph = graph()
stock_graph.price_graph()
root.mainloop()
Any help would be much appreciated.
Reply
#2
Try running animation without using tkinter. Can you make that work?

I don't understand the reason of using funcanimation inside tkinter. It mskes sense in matplotlib where it is the only way to add an update loop to a graph. In tkinter, or any gui toolkit for that matter, I would use a timer event to periodically update the graph. In Tk this is done with .after
Reply
#3
(Mar-11-2024, 03:33 PM)deanhystad Wrote: Try running animation without using tkinter. Can you make that work?

I don't understand the reason of using funcanimation inside tkinter. It mskes sense in matplotlib where it is the only way to add an update loop to a graph. In tkinter, or any gui toolkit for that matter, I would use a timer event to periodically update the graph. In Tk this is done with .after

Hi, thanks for your reply.

The animation works fine without tkinter.

In the context of my project, I am working on a Stock simulation that uses Tkinter (Custom Tkinter to be exact), and I have used web scraping to get price information from a website and add this to a csv file to plot with matplotlib. I haven't seen any other ways of embedding graphs to tkinter and thats why I am using matplotlib and FuncAnimation.

The following code works as I want it to parallel with the web scraping I was on about:
plt.style.use('fivethirtyeight')
root = tkinter.Tk()
root.title("Embed Animation to Tkinter frame")
root.geometry("800x400")
root.resizable(False,False)


graph_frame = tkinter.Frame(master=root, width=500, height=300)
graph_frame.pack()

fig = plt.Figure(dpi=70, constrained_layout=True)
ax = fig.add_subplot(111)
plt.rcParams.update({'font.size':10})

def animate(i):
  df = pd.read_csv('data.csv', skiprows=1)
  list_of_rows = [list(row) for row in df.values]
    
  xval = []
  yval = []
    
  for eachValue in list_of_rows:
    x,y = eachValue[0],eachValue[1]
    xval.append(int(x))
    yval.append(float(y))
  
  ax.clear()
  ax.plot(xval, yval, lw=2)
  
  return [ax]
      
canvas = FigureCanvasTkAgg(fig, master=graph_frame)
canvas.draw()
canvas.get_tk_widget().pack()
canvas._tkcanvas.pack()

anim = FuncAnimation(fig, animate, interval=1000, blit=False)
tkinter.mainloop()
But as soon as I try and use it with OOP and classes in my project, it gives me the following error:
Error:
UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g. `anim`, that exists until you output the Animation using `plt.show()` or `anim.save()`. warnings.warn(
I am struggling to figure out why it works normally, but doesn't work when I introduce it as a class? Huh
Reply
#4
The likely problem is that there is no variable referencing the animation. "anim" is a local variable that disappears as soon as the price_graph() method ends. You need to make this an instance variable.

I stripped out everything that isn't absolutely necessary to focus on what is.
import pandas as pd
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

class Graph(tk.Tk):
    def __init__(self, *args, **kwargs):
        def animate(_):
            df = pd.read_csv('data.csv', names=("x", "y"), skiprows=1)  # Does first row have a bad header?
            ax.clear()
            ax.plot(df.x, df.y)  # Should not need to call float or int to convert dataframe values.

        super().__init__(*args, **kwargs)
        fig = plt.Figure()
        ax = fig.add_subplot()
        canvas = FigureCanvasTkAgg(fig, master=self)
        canvas.get_tk_widget().pack()
        self.anim = FuncAnimation(fig, animate, interval=1000)  # self.anim makes this an instance variable instead of local variable.

Graph().mainloop()
A lot of things could be different. Instead of subclassing Tk to make a Graph top level window, you could subclass Frame and put the graph in the frame. This would allow multiple plots in one window.

It probably makes more sense making animate() a method instead of an enclosed function, This would require changing ax to an instance variable so it could be used in both __init__() and animate().

I till thing using funcanimation is the wrong way to do this. In this post I used tkinter.after to update th graph.

https://python-forum.io/thread-39130-pos...#pid166027

I was wrong about funcanimation blocking, but I still think there are advantages to having the program do the updates with a timer event. For example, it would be simple to tie the animation to a scrollbar for scrolling forward and backward in time. Starting and stopping the animation is simpler when you don't have to work around funcanimation.

For an apples to apples comparison, here's the same program written using funcanimation and using after().

Using funcanimation:
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.animation import FuncAnimation
import math


class Graph(tk.Tk):
    """Tkinter window that displays matplotlib plot."""
    def __init__(self, *args, **kwargs):
        def animate(i):
            self.x.append(self.x[-1] + 0.1)
            self.y.append(math.sin(self.x[-1]))
            plt.clear()
            plt.plot(self.x[-100:], self.y[-100:])

        super().__init__(*args, **kwargs)
        fig = Figure()
        plt = fig.add_subplot()
        canvas = FigureCanvasTkAgg(fig, master=self)
        canvas.get_tk_widget().pack()
        self.x = [0]
        self.y = [0]
        self.anim = FuncAnimation(fig, animate, interval=10)


Graph().mainloop()
Using after()
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import math


class Graph(tk.Tk):
    """Tkinter window that displays matplotlib plot."""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.figure = Figure()
        self.plt = self.figure.add_subplot()
        canvas = FigureCanvasTkAgg(self.figure, self)
        canvas.get_tk_widget().pack()
        self.x = [0]
        self.y = [0]
        self.animate()

    def animate(self):
        self.x.append(self.x[-1] + 0.1)
        self.y.append(math.sin(self.x[-1]))
        self.plt.clear()
        self.plt.plot(self.x[-100:], self.y[-100:])
        self.figure.canvas.draw()
        self.after(10, self.animate)


Graph().mainloop()
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Interaction between Matplotlib window, Python prompt and TKinter window NorbertMoussy 3 541 Mar-17-2024, 09:37 AM
Last Post: deanhystad
  [Tkinter] cutomtkinter matplotlib no x,y - xaxis and x,y - labels-> only graph and grid visible dduric 0 331 Feb-20-2024, 07:09 PM
Last Post: dduric
  How to use rangesliders feature from matplotlib in tkinter? pymn 2 2,989 Feb-28-2022, 05:06 PM
Last Post: pymn
  [PyQt] Refresh x-labels in matplotlib animation widget JohnT 5 3,791 Apr-23-2021, 07:40 PM
Last Post: JohnT
  Tkinter Matplotlib Nepo 1 2,479 Sep-27-2020, 10:20 AM
Last Post: Gribouillis
  Tkinter & matplotlib PillyChepper 9 5,738 Nov-23-2019, 10:36 AM
Last Post: PillyChepper
  Tkinter Animation Evil_Patrick 3 2,563 Nov-04-2019, 06:56 PM
Last Post: Larz60+
  [Tkinter] how to remove black area around the graph in tkinter ? NEL 1 2,304 Aug-03-2019, 01:48 PM
Last Post: NEL
  Axis lim and Plotting a graph in Tkinter KEDivergente 0 1,743 May-21-2019, 08:10 PM
Last Post: KEDivergente
  Dynamic graph matplotlib quant7 1 4,156 May-17-2019, 06:24 PM
Last Post: quant7

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020