DenGor
DenGor

Reputation: 205

continuously updating graph in tkinter

I have the following python code (in PyCharm) that I use to take readings from an Arduino board. The readings themselves are fine. I have the two following problems with the tkinter part of the code:

  1. The code begins to read in values from Arduino as soon as it is launched, whereas I want to initiate this on a button click ('read_data'); as long as I don't press the 'read_data' button, the graph is not displayed, but readings are taken; I can see that when I open the graph several seconds after I begin running the code;
  2. When I close the plot close_plot, the graph window is indeed closed, only to be re-opened a short while later.

I think the problem lies in Top.after as it is continuously run within the mainloop() and, therefore, the read_data function is continuously updated. How can I get around this?

import serial
from tkinter import *
from matplotlib import pyplot as plt

Top = Tk()

ser = serial.Serial('COM3', baudrate=9600, timeout=1)

x = []
y = []

def read_data():
    plt.ion()
    new_value = ser.readline().decode('ascii')
    if new_value == '':
        pass
    else:
        y.append(eval(new_value[:-2]))
        x.append(len(y) - 1)
        plt.plot(x, y, 'r-')
        plt.show()
        plt.pause(0.0001)
        Top.after(100, read_data)

def close_plot():
    plt.close()
    global x, y
    x = []
    y = []

def quit():
    Top.destroy()

Button(Top, text='Read', command=read_data).pack()
Button(Top, text='Close plot', command=close_plot).pack()
Button(Top, text='Quit', command=quit).pack()

Top.after(100, read_data)
mainloop()

Edit: when pressing the read_data button, I get the following warning:

C:\ProgramData\Anaconda3\lib\site-packages\matplotlib\backend_bases.py:2445: MatplotlibDeprecationWarning: Using default event loop until function specific to this GUI is implemented warnings.warn(str, mplDeprecation)

Upvotes: 1

Views: 3660

Answers (1)

Nae
Nae

Reputation: 15335

First, remove the line:

Top.after(100, read_data)

that comes immediately before mainloop() like furas suggested.

Then add after_cancel method to stop calling read_data every 100 ms, but for that to work, we need to be assigning after we use inside the method to a global variable first:

func_id = Top.after(100, read_data)

and then finally call after_cancel in close_plot:

Top.after_cancel(func_id)

Your code should be exactly like below:

import serial
from tkinter import *
from matplotlib import pyplot as plt

Top = Tk()

ser = serial.Serial('COM3', baudrate=9600, timeout=1)

x = []
y = []
func_id = None

def read_data():
    global func_id
    plt.ion()
    new_value = ser.readline().decode('ascii')
    if new_value == '':
        pass
    else:
        y.append(eval(new_value[:-2]))
        x.append(len(y) - 1)
        plt.plot(x, y, 'r-')
        plt.show()
        plt.pause(0.0001)
    func_id = Top.after(100, read_data)

def close_plot():
    global func_id
    #to no longer update the plot
    Top.after_cancel(func_id)
    plt.close()
    global x, y
    del x[:]
    del y[:]

def quit():
    Top.destroy()

Button(Top, text='Read', command=read_data).pack()
Button(Top, text='Close plot', command=close_plot).pack()
Button(Top, text='Quit', command=quit).pack()

mainloop()

Upvotes: 1

Related Questions