Search code examples
pythonmatplotlibtkintercustomtkintermatplotlib-animation

Figure doesn't update after regeneration


I am trying to create an updating graph, I want to be able to control when and how often it updates. This graph will need to output data with specified time gaps as to not be too slow nor too fast that it clogs the gui. It is paramount that it isn't performance heavy.

To achieve this I create a viewable window in customtkinter create a reset button and a frame for the graph and then generate the graph to go inside it, whilst generating the graph I create plots to go inside it.

Once this is done it will start looping by creating new threads with a one second delay as to always keep the graph consistant. This loop sets the data of the lines that were generated and then plt.ion allows these new lines to be visible on the graph.

However, when regenerating this frame and all its contents (call: regen_graph()) the graph plots the intial set of coordinates but no more. They are being added but not shown, as far as I can tell.

import customtkinter as ct
from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import threading

class App(ct.CTk):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # Configure window
        self.geometry("800x600")

        # Variables
        self.loop_count = 0
        self.line_dict_ = {}
        self.data_store = ([1, 2], [3, 2])

        # Widgets
        self.regen_button = ct.CTkButton(self, text="Regen", command=self.regen_graph)
        self.regen_button.pack()
        self.graph_frame = ct.CTkFrame(self)
        self.graph_frame.pack()

        # Functions
        self.gen_graph(self.graph_frame)

    # Initial
    def gen_graph(self, master):
        # Figure Declaration
        self.fig, self.ax1 = plt.subplots(layout='constrained')
        self.ax1.set_xlim(0, 100)
        self.ax1.set_ylim(0, 100)

        # Set interactive
        plt.ion()

        # Create Canvas with figure in frame
        canvas = FigureCanvasTkAgg(self.fig, master=master)
        canvas.get_tk_widget().pack()

        # Create Plots
        for name in ["1", "2"]:
            self.line_dict_[name], = self.ax1.plot(*self.data(), scaley=True, scalex=True, label=name)

        self.loop_count += 1
        self._update_loop(self.loop_count)

    # Loop
    def _update_loop(self, num):
        if self.loop_count == num:
            print("Thread: ", threading.current_thread())
            # Prep Next Loop
            self.graph_thread = threading.Timer(1, lambda x=num: self._update_loop(x))
            self.graph_thread.daemon = True
            self.graph_thread.start()

            # Apply new data to lines
            for key in self.line_dict_:
                # Apply the data from the key to the keys line
                self.line_dict_[key].set_data(*self.data(True))

    # Functions
    def regen_graph(self):
        print("resetting graph")
        # Clear Graph Frame
        plt.ioff()
        self.fig.clf()
        self.graph_frame.destroy()

        # New Graph Frame
        self.graph_frame = ct.CTkFrame(self)
        self.graph_frame.pack()
        self.gen_graph(self.graph_frame)

    def data(self, add=False):
        if add:
            self.data_store[0].append(self.data_store[0][-1] + 1)
            self.data_store[1].append(self.data_store[1][-1] + 1)
        return self.data_store

app = App()
app.mainloop()

I've tried:

  • Changing customtkinter to tkinter. Didn't work, there was no change.
  • Swapping the preb next loop section for self.after(1000, lambda x=num: self._update_loop(x)). This returned the same results.
  • Changing when and if the plt.ion() and plt.ioff() were called.
    • When I got rid of the plt.ioff() in the regen_graph function I found that it did continue updating but there was another graph drawn in a different window beside it.
    • Then I added plt.close() to the end of the gen_graph function and this worked however the graph would still pop in and out of the screen and I worry about what its doing in the background, especially with performance being a priority.

Solution

  • You can update any matplotlib figure in python by using fig.canvas.draw()

    Similar like how you are doing self.fig.clf(), except it will be self.fig.canvas.draw(). This will update the figure inside the loop