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:
self.after(1000, lambda x=num: self._update_loop(x))
. This returned the same results.plt.ion()
and plt.ioff()
were called.
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.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.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