Your data is too fast for this code to plot because it is doing a lot of stuff on every data received. Here are some solutions to fix your issues:
- Avoid clearing the Plot every time. It significantly reduces the speed of GUI. Remove the use of
ax.cla() to prevent unnecessary clearing of the plot.
- Avoid creating new lines every time the data is updated. Use
Line2D object. Create it once during initialization and reuse it for all the updates.
- Instead of plotting all the data points from scratch on every update, use
set_xdata() and set_ydata() to update the existing line with new data directly. This is reducing the processing time significantly.
- Use a fixed-sized window instead of accumulating the data indefinitely.
- One of the major reasons could be
self.figure_agg.draw() as it may block the main thread resulting in slow down the GUI. Use self.figure_agg.draw_idle() instead.
Bonus: Make a dynamically adjusted axis limits using set_xlim() and set_ylim() to keep the plot centered on the latest data that enhances the visual experience.
I did not have a proper setup to generate this data from a Bluetooth device so I simulated the data using Python only with the help of AI. This is the final code that includes all the above-mentioned points.
# Importing necessary libraries
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
import random
import threading
import time
# Class for managing data plotting
class DataPlot:
def __init__(self, ax, title, x_label, y_label, window_size=50):
# Setting up the plot with given parameters
self.ax = ax
self.title = title
self.x_label = x_label
self.y_label = y_label
self.window_size = window_size # Controls the number of points shown in the moving window
self.line, = self.ax.plot([], [], 'b-', linewidth=1) # Initialize the plot line
self.ax.grid()
self.ax.set_title(self.title)
self.ax.set_xlabel(self.x_label)
self.ax.set_ylabel(self.y_label)
# Initialize empty lists to hold data
self.x_data = []
self.y_data = []
def update(self, new_x, new_y):
# Add new data points to the current list
self.x_data.extend(new_x)
self.y_data.extend(new_y)
# Keep only the latest `window_size` number of points
if len(self.x_data) > self.window_size:
self.x_data = self.x_data[-self.window_size:]
self.y_data = self.y_data[-self.window_size:]
# Update the line data
self.line.set_xdata(self.x_data)
self.line.set_ydata(self.y_data)
# Adjust the plot limits to keep the new data centered
self.ax.set_xlim(min(self.x_data), max(self.x_data))
self.ax.set_ylim(min(self.y_data) - 10, max(self.y_data) + 10) # Dynamically adjust y-limits
# Redraw the plot without blocking the GUI
self.line.figure.canvas.draw_idle()
class View:
def __init__(self, figure, ax):
self.figure = figure
self.ax = ax
self.well1_plot = DataPlot(ax, "Moving Line Plot", "Time", "Value")
def update_demo_plots(self, calibration_sweeps):
# Prepare data for plotting
cal_x = [i for i in range(len(calibration_sweeps))] # Simulating time on the x-axis
cal_y = [sweep['resistance'] for sweep in calibration_sweeps] # Plotting resistance values
self.well1_plot.update(cal_x, cal_y)
# Function to simulate incoming data
def data_generator(view):
calibration_sweeps = [] # This list will store incoming data points
while True:
time.sleep(0.02) # Simulate data arrival every 20 milliseconds
# Generate random data points
new_data = {'voltage': random.uniform(0, 5), 'resistance': random.uniform(10, 100)}
calibration_sweeps.append(new_data)
# Limiting the number of data points to keep the plot responsive
if len(calibration_sweeps) > 50: # Keep the last 50 points
calibration_sweeps.pop(0)
# Updating the plot with new data
view.update_demo_plots(calibration_sweeps)
# Setting up the GUI window with PySimpleGUI
layout = [[sg.Canvas(key='-CANVAS-')], [sg.Button('Exit')]]
window = sg.Window('Real-time Plotting', layout, finalize=True)
fig, ax = plt.subplots()
view = View(fig, ax)
figure_agg = FigureCanvasTkAgg(fig, window['-CANVAS-'].TKCanvas)
figure_agg.draw()
figure_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
thread = threading.Thread(target=data_generator, args=(view,), daemon=True)
thread.start()
while True:
event, values = window.read(timeout=10)
if event == sg.WIN_CLOSED or event == 'Exit':
break
window.close()
This updates the plot very quickly. Let me know if anything is not clear or breaks down during your implementation.