Search code examples
pythonmatplotlibtkintermatplotlib-animation

the matplotlib graph is breaking after my custom x axis labels exceed 50


i am developing a weather monitoring program and i am stuck at a problem. the problem is when the array that contains the labels for the x axis exceeds the lenght of 50 the graph starts to break. and i need to set the x values to a custom array because it contains the exact time when was the measurmet executed

I would be happy if somebody could hep me solve the problem.

my code:

#made by danoo
import tkinter as tk
from tkinter import ttk
import threading
from random import randint
from time import sleep, time
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np

def create_graph(root, height0, height1, width, gridX, gridY):
    fig, ax = plt.subplots(figsize=(10, 3))
    ax.set_ylim(height0, height1)
    x = range(width)
    y = [0] * width
    line, = ax.plot(x, y)

    # Create a frame for the graph
    graph_frame = ttk.Frame(root)
    graph_frame.place(x=gridX, y=gridY)

    # Add the graph to the frame
    canvas = FigureCanvasTkAgg(fig, master=graph_frame)
    canvas.get_tk_widget().pack(fill=tk.BOTH, expand=tk.YES)

    return line, canvas

def update_graph(line, data, canvas, width, x_values, height1, height0):
    if height1 != 0:
        line.axes.set_ylim([height0, height1])
    
    y = list(data[-width:])
    x = range(len(y))
    line.set_data(x, y)
    line.axes.relim()
    line.axes.autoscale_view(False, True, True)
    line.axes.set_xlim([0, len(y)])
    xses = list(x_values[-width:])
    # Set x-tick labels to display every 10th value
    x_labels = [str(x_val) if x_val % 10 == 0 else '' for x_val in xses]
    line.axes.set_xticks(xses)
    line.axes.set_xticklabels(x_labels)

    canvas.draw()


def communication(label, line, canvas):
        m = []
        x = []
        i = 0
        while True:
            sleep(0.2)
            recrived = randint(0,100)
            m.append(recrived)
            x.append(i)
            i+=1
            update_graph(line, m, canvas, 50, x, 100, 0)#might be bug !!!!!!!!!!



def main():
    root = tk.Tk()
    root.geometry("1024x600")

    line, canvas = create_graph(root,10,40,50, 0 , 280)

    comm_thread = threading.Thread(target=communication, args=(0,line, canvas))
    comm_thread.start()
    
    
    root.mainloop()
    comm_thread.join()

if __name__ == "__main__":
    main()


bassicly what this program does it cretes a graph and puts it onto the window. then the communication thread updates the graph. and in the update_graph function it gets the last 50 values from the data and the time arrays then it draws the data on the graph and gets every 10 value from the time array and and displays it to the x axis of the graph. and when the leght of time exceeds 50 the graph breaks. the problem is i think in the update_graph function but idk what.


Solution

    1. If you take the last 50 y values, then of course you must also take the last 50 x values.
    y = list(data[-width:]) -> y = data[-width:]
    x = range(len(y)) -> x = x_values[-width:]
    

    A slice is a list anyway.

    1. The x range on the chart must also take the last 50 values.
    line.axes.set_xlim([0, len(y)]) -> line.axes.set_xlim([x[0], x[-1]])
    
    1. To avoid a memory leak, we need to truncate the m and x lists in the communication function.
    import tkinter as tk
    from tkinter import ttk
    import threading
    from random import randint
    from time import sleep, time
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    import numpy as np
    
    
    def create_graph(root, height0, height1, width, gridX, gridY):
        fig, ax = plt.subplots(figsize=(10, 3))
        ax.set_ylim(height0, height1)
        x = range(width)
        y = [0] * width
        line, = ax.plot(x, y)
    
        # Create a frame for the graph
        graph_frame = ttk.Frame(root)
        graph_frame.place(x=gridX, y=gridY)
    
        # Add the graph to the frame
        canvas = FigureCanvasTkAgg(fig, master=graph_frame)
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=tk.YES)
    
        return line, canvas
    
    
    def update_graph(line, y, canvas, x, height1, height0):
        if height1 != 0:
            line.axes.set_ylim([height0, height1])
    
    
        line.set_data(x, y)
    
        line.axes.set_xlim([x[0], x[-1]])
    
        # Set x-tick labels to display every 10th value
        x_labels = [str(x_val) if x_val % 10 == 0 else '' for x_val in x]
        line.axes.set_xticks(x)
        line.axes.set_xticklabels(x_labels)
    
        canvas.draw()
    
    
    def communication(label, line, canvas, width):
        m = []
        x = []
        i = 0
        while True:
            sleep(0.2)
            recrived = randint(0, 100)
            m.append(recrived)
            x.append(i)
            print(m, x)
            i += 1
            m = m[-width:]
            x = x[-width:]
            update_graph(line, m, canvas, x, 100, 0)  # 
    
    
    def main():
        root = tk.Tk()
        root.geometry("1024x600")
    
        line, canvas = create_graph(root, 10, 40, 50, 0, 280)
    
        comm_thread = threading.Thread(target=communication, args=(0, line, canvas, 50))
        comm_thread.start()
    
        root.mainloop()
        comm_thread.join()
    
    
    if __name__ == "__main__":
        main()
    

    I think that we can do without threads by replacing the loop while and the sleep(0.2) with the after method.