Tkinter Label not being updated with textvariable - python

In my code I see that the Label is not being updated with the 'textvariable', despite I believe I'm doing it right (probably not!).
varmuTemperature = StringVar(value="default value")
self.Label = Label(Frame2, textvariable = varmuTemperature)
self.Label.pack()
This should show a label with "default value" written on it. The problem is that I don't see anything written.
I have my code posted here.
import matplotlib
import matplotlib.artist as artists
import matplotlib.pyplot as plt
#import matplotlib.mlab as mlab
import scipy.stats
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,
NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
import numpy as np
import statistics
from tkinter import *
from tkinter import ttk
import serial
import time
import itertools
integer=0
xList = []
humidityList = []
humidityListHistogram = []
temperatureList = []
temperatureListHistogram = []
cnt=0
if sys.platform.startswith('win'):
ports = ['COM%s' % (i + 1) for i in range(256)]
elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
# this excludes your current terminal "/dev/tty"
ports = glob.glob('/dev/tty[A-Za-z]*')
elif sys.platform.startswith('darwin'):
ports = glob.glob('/dev/tty.*')
else:
raise EnvironmentError('Unsupported platform')
ser = serial.Serial()
style.use("seaborn-whitegrid")
#varmuTemperature = StringVar()
#varmuHumidity = StringVar()
f=plt.figure(0, figsize=(20,10))
humidityGraph = plt.subplot(224)
humidityGraph.set_title("Humidity vs Time")
humidityGraph.set_ylabel("Humidity RAW (Dec)")
humidityGraph.set_xlabel("Sample ()")
temperatureGraph = plt.subplot(223)
temperatureGraph.set_title("Temperature vs Time")
temperatureGraph.set_ylabel("Temperature RAW (Dec)")
temperatureGraph.set_xlabel("Sample ()")
humidityGraphHistogram = plt.subplot(222)
temperatureGraphHistogram = plt.subplot(221)
temperatureGraphHistogramNormal = temperatureGraphHistogram.twinx()
humidityGraphHistogramNormal = humidityGraphHistogram.twinx()
side_text = plt.figtext(0.93, 0.5, 'Text 1'+'\n'+'Text 2', bbox=dict(facecolor='white'))
plt.subplots_adjust(left = 0.05, right = 0.95, bottom = 0.05, top = 0.95, wspace = 0.16, hspace = 0.21)
class make_window():
def __init__(self, *args, **kwargs):
win = Tk()
win.title("Test")
win.state("zoomed")
Frame1 = Frame(win)
Frame1.pack()
self.comboBoxAvailableCOMPort = ttk.Combobox(Frame1, width = 30)
self.comboBoxAvailableCOMPort['values'] = []
self.comboBoxAvailableCOMPort.pack(padx=5, pady=5, side = LEFT)
self.buttonCheckComAvailable = Button(Frame1, text="Check COM Available", command = self.CheckComAvailable)
self.buttonCheckComAvailable.pack(padx=5, pady=10, side = LEFT)
self.buttonOpenCOMPort = Button(Frame1, text="Open COM Port", command = self.OnOpenCom)
self.buttonOpenCOMPort.pack(padx=5, pady=10, side = LEFT)
self.buttonCloseCOMPort = Button(Frame1, text="Close COM Port" , command = self.OnCloseCOM)
self.buttonCloseCOMPort.pack(padx=5, pady=10,side = LEFT)
self.CheckComAvailable()
Frame2 = Frame(win, highlightbackground = "red", highlightcolor = "red", highlightthickness = 1)
Frame2.pack()
varmuTemperature = StringVar(value="default value")
varmuTemperature.set("trerta")
print(varmuTemperature.get())
self.Label = Label(Frame2, textvariable = varmuTemperature)
self.Label.pack()
self.buttonCloseProgram = Button(Frame2, text="Close Program", command = self.OnCloseProgram)
self.buttonCloseProgram.pack(expand=True, fill='x', anchor='s')
Frame3 = Frame(win)
Frame3.pack()
canvas = FigureCanvasTkAgg(f, Frame3)
canvas.get_tk_widget().pack(padx=5, pady=10, side=BOTTOM, expand = True)
toolbar = NavigationToolbar2Tk(canvas, Frame3)
toolbar.update()
canvas._tkcanvas.pack(padx=5, pady=10,side = TOP)
def CheckComAvailable(self):
self.comboBoxAvailableCOMPort['values'] =[]
result = []
for port in ports:
try:
s = serial.Serial(port)
s.close()
result.append(port)
except (OSError, serial.SerialException):
pass
self.comboBoxAvailableCOMPort['values'] += tuple(result)
self.comboBoxAvailableCOMPort.set(result[0])
def OnOpenCom(self):
ser.baudrate = 115200
ser.port = self.comboBoxAvailableCOMPort.get()
try:
ser.open()
ser.readline()
ser.write("#dut,0$\n".encode('utf-8'))
ser.readline()
ser.write("#v,1800,1800$\n".encode('utf-8'))
ser.write("#W,77,08,07$\n".encode('utf-8'))
ser.readline()
except(OSError):
print("COM Port in use")
def OnCloseCOM(self):
global xList
global humidityList
global temperatureList
global humidityListHistogram
global temperatureListHistogram
global integer
integer=0
xList = []
humidityList = []
temperatureList = []
ser.close()
def OnCloseProgram(self):
self.OnCloseCOM()
exit()
## def toggle_geom(self,event):
## geom=self.master.winfo_geometry()
## print(geom,self._geom)
## self.master.geometry(self._geom)
## self._geom=geom
def animate(i):
global integer
global cnt
try:
ser.write(("#R,77,00,03$" + chr(10)).encode('utf-8'))
humidityLine=ser.readline()
inthumidityLine= int(humidityLine,16)
if (inthumidityLine > 8388608):
inthumidityLine = inthumidityLine - 16777216
humidityList.append(inthumidityLine)
humidityListHistogram.append(inthumidityLine)
ser.write(("#R,77,03,03$" + chr(10)).encode('utf-8'))
temperatureLine=ser.readline()
LineHistogram = temperatureLine
inttemperatureLine= int(temperatureLine,16)
if (inttemperatureLine > 8388608):
inttemperatureLine = inttemperatureLine - 16777216
temperatureList.append(inttemperatureLine)
temperatureListHistogram.append(inttemperatureLine)
xList.append(integer)
integer+=1
##################################################################################################################
## Creates the HUMIDITY Graphics
##################################################################################################################
humidityGraph.clear()
humidityGraph.plot(xList,humidityList,'-b*', label = "Humidity RAW")
humidityGraph.legend(loc='upper right', fancybox = True, frameon = True, shadow = True)
humidityGraph.set_title("Humidity vs Time")
humidityGraph.set_ylabel("Humidity RAW (Dec)")
humidityGraph.set_xlabel("Sample ()")
muHumidity = statistics.mean(humidityListHistogram)
#print("Mean = " + str(muHumidity) + " ; N = " + str(len(humidityListHistogram)))
#global varmuHumidity
#varmuHumidity.set("Humidity: ")
if (len(humidityListHistogram) > 1):
sigmaHumidity = statistics.pstdev(humidityListHistogram)
else:
sigmaHumidity = 100
humidityGraphHistogram.clear()
nHumidity, binsHumidity, patchesHumidity = humidityGraphHistogram.hist(humidityListHistogram, 100, density=False, facecolor='blue', alpha=0.75, histtype = 'stepfilled')
normalDistHumidity = scipy.stats.norm.pdf(binsHumidity, muHumidity, sigmaHumidity)
humidityGraphHistogramNormal.clear()
humidityGraphHistogramNormal.plot(binsHumidity, normalDistHumidity, 'r--')
humidityGraphHistogram.set_title("Histogram for Humidity Data")
humidityGraphHistogram.set_ylabel("Humidity RAW Counts (Dec)")
humidityGraphHistogram.set_xlabel("BINS ()")
humidityGraphHistogramNormal.set_ylabel("Normal Distribution")
##################################################################################################################
## Creates the TEMPERATURE Graphics
##################################################################################################################
temperatureGraph.clear()
temperatureGraph.plot(xList,temperatureList,'-r*', label = "Temperature RAW")
temperatureGraph.legend(loc='upper right', fancybox = True, frameon = True, shadow = True)
temperatureGraph.set_title("Temperature vs Time")
temperatureGraph.set_ylabel("Temperature RAW (Dec)")
temperatureGraph.set_xlabel("Sample ()")
muTemperature = statistics.mean(temperatureListHistogram)
#global varmuTemperature
#varmuTemperature.set("Temperature: " )
if (len(temperatureList) > 1):
sigmaTemperature = statistics.pstdev(temperatureListHistogram)
else:
sigmaTemperature = 100
temperatureGraphHistogram.clear()
nTemperature, binsTemperature, patchesTemperature = temperatureGraphHistogram.hist(temperatureListHistogram, 100, density=False, facecolor='red', alpha=0.75, histtype = 'stepfilled')
normalDistTemperature = scipy.stats.norm.pdf(binsTemperature, muTemperature, sigmaTemperature)
temperatureGraphHistogramNormal.clear()
temperatureGraphHistogramNormal.plot(binsTemperature, normalDistTemperature, 'b--')
temperatureGraphHistogram.set_title("Histogram for Temperature Data")
temperatureGraphHistogram.set_ylabel("Temperature RAW Counts (Dec)")
temperatureGraphHistogram.set_xlabel("BINS ()")
temperatureGraphHistogramNormal.set_ylabel("Normal Distribution")
if (cnt > 100):
xList.pop(0)
humidityList.pop(0)
temperatureList.pop(0)
cnt+=1
except(OSError):
bla=0
win = make_window()
ani = animation.FuncAnimation(f, animate, interval = 300)
make_window.mainloop()
Debugging a bit and start commenting lines of code, I see that the problem may come from the
f=plt.figure(0, figsize=(20,10))
Commenting this line (and all dependencies of this) makes the Label to be written.
Can someone help here, please? I don't get why the graphics can interfere in the Label.
Thanks a lot.

The general problem seems to be your management of instance variables and objects. You keep around as self.* variables, things you'll never need to reference again, like buttonCheckComAvailable, but fail to make self.* variables for things, like varmuTemperature, that you will need to reference later.
Object-wise, you do things that don't make sense:
make_window.mainloop()
as make_window is an object class, not an instance, and an instance of the class make_window won't respond to mainloop anyway as it contains a window but isn't one itself.
Here's my MCVE for your example code that makes varmuTemperature an instance variable and, just for demonstration purposes, sets it when the various buttons on the interface are clicked so you can see it's working:
from tkinter import *
from tkinter import ttk
class make_window():
def __init__(self):
self.win = Tk()
self.win.title("Test")
self.win.state("zoomed")
Frame1 = Frame(self.win)
self.comboBoxAvailableCOMPort = ttk.Combobox(Frame1, width=30)
self.comboBoxAvailableCOMPort['values'] = []
self.comboBoxAvailableCOMPort.pack(padx=5, pady=5, side=LEFT)
Button(Frame1, text="Check COM Available", command=self.CheckComAvailable).pack(padx=5, pady=10, side=LEFT)
Button(Frame1, text="Open COM Port", command=self.OnOpenCom).pack(padx=5, pady=10, side=LEFT)
Button(Frame1, text="Close COM Port", command=self.OnCloseCom).pack(padx=5, pady=10, side=LEFT)
Frame1.pack()
Frame2 = Frame(self.win, highlightbackground="red", highlightcolor="red", highlightthickness=1)
self.varmuTemperature = StringVar(value="default value")
Label(Frame2, textvariable=self.varmuTemperature).pack()
Button(Frame2, text="Close Program", command=self.OnCloseProgram).pack(expand=True, fill='x', anchor='s')
Frame2.pack()
def CheckComAvailable(self):
self.varmuTemperature.set("CheckCom")
def OnOpenCom(self):
self.varmuTemperature.set("OpenCom")
def OnCloseCom(self):
self.varmuTemperature.set("CloseCom")
def OnCloseProgram(self):
self.OnCloseCom()
exit()
window = make_window()
window.win.mainloop()

Related

DHT22 Temp sensor causing Tkinter GUI to lag

Hi I've been trying to implement a DHT22 temperature and humidity sensor with Tkinter GUI where it updates regularly while using the GUI. This is done on a Raspberry Pi 4 using Python. However, while the numbers are updating correctly, it would cause the GUI freeze momentarily while updating the numbers. I have already tried multithreading, but it unfortunately didn't help. Is there any solution to this problem? Any help is greatly appreciated.
Here's the code:
# Import the required libraries
import RPi.GPIO as GPIO
import spidev
import time
import tkinter
from tkinter import *
import tkinter.font as tkFont
from PIL import ImageTk, Image
# import time
import smbus
import serial
import os
import argparse
import Adafruit_DHT
from threading import Thread
SENSOR = Adafruit_DHT.DHT22
# GPIO4 on the Raspberry Pi
SENSOR_PIN = 4
address = 0b01
ssPin = 8
spi = spidev.SpiDev()
spi.open(0,0)
spi.max_speed_hz = 976000
spi.mode = 0b11
#spi.bit_order = msbfirst
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(ssPin,GPIO.OUT)
GPIO.setup(22,GPIO.OUT)
GPIO.setup(26,GPIO.OUT)
GPIO.output(ssPin,GPIO.LOW)
GPIO.output(22,GPIO.HIGH)
GPIO.output(26,GPIO.HIGH)
class App:
def __init__(self, master):
def SendScaleReading(self):
S = scale.get() #retrieve value from slider
if S>0:
S+=155
print(S)
spi.xfer([address ,S]) #write data to slave address
frame = Frame(master)
frame.pack()
scale = tkinter.Scale(root,from_=0,to=100,length=700,bg='black',fg='#0000FF', highlightthickness = 0, bd = 0, command=SendScaleReading) #set parameters of slider
scale.place(relx=0.75, rely=0.05)
#scale.place(relx = 0, rely = 0) # set position of slider
fontstyle = tkFont.Font(family='Arial', size=50) #initialise font
scale['font'] = fontstyle
def PowerFn():
global powerBool
# print(ledBool)
if powerBool:
#print('went to on button')
powerBtn.config(image=On_BtnImg)
powerBtn.image = On_BtnImg
#print("on button configured")
powerLabel.config(text="POWER: ON", fg='#00FF00')
# communicating with arduino
GPIO.output(26,GPIO.LOW)
else:
#print('went to off button')
powerBtn.config(image=Off_BtnImg)
powerBtn.image = Off_BtnImg
#print("off button configured")
powerLabel.config(text="POWER: OFF", fg='#FF0000')
# communicating with arduino
GPIO.output(26,GPIO.HIGH)
powerBool = not powerBool
def DirectionFn():
global directionBool
# print(cbBool)
if directionBool:
#print('went to CbOn button')
directionBtn.config(image = On_BtnImg)
directionBtn.image = On_BtnImg
#print("CbOn button configured")
directionLabel.config(text="FORWARD", fg='#00FF00')
# communicating with arduino
GPIO.output(22,GPIO.HIGH)
else:
#print('went to CbOff button')
directionBtn.config(image = Off_BtnImg)
directionBtn.image = Off_BtnImg
# print("CbOff button configured")
directionLabel.config(text="REVERSE", fg='#FF0000')
# communicating with arduino
GPIO.output(22,GPIO.LOW)
directionBool = not directionBool
def tempsensor():
while True:
print("bruh moment")
h, t = Adafruit_DHT.read_retry(SENSOR, SENSOR_PIN)
print(t)
print(h)
temp = "%.1F" %t
hum = "%.1f" %h
temperature.set(temp+ " *C")
humidity.set(hum+ " %")
time.sleep(1);
root = Tk()
app = App(root)
root.config(bg='black')
#root.attributes('-zoomed', True)
#root.state('fullscreen')
rootWidth = root.winfo_screenwidth()
rootHeight = root.winfo_screenheight()
root.attributes('-zoomed', True)
# Create mini window
#canvas = Canvas(root, bg='black', highlightbackground='white')
#canvas.place(relx=0.1, rely=0.03, relheight=0.51, relwidth=0.505)
temperature = StringVar()
temperature.set("----"+" *C")
humidity = StringVar()
humidity.set("----"+" %")
#root.after(2000, tempsensor)
h, t = Adafruit_DHT.read_retry(SENSOR, SENSOR_PIN)
On_img = Image.open("/home/pi/Downloads/on.png")
Off_img = Image.open("/home/pi/Downloads/off.png")
# Resize the image using resize() method according to the screen height and width
btnWidth = int(rootWidth / 6.4)
print(btnWidth)
infobtnWidth = int(rootHeight / 10)
print(infobtnWidth)
On_resize_img = On_img.resize((btnWidth, btnWidth))
Off_resize_img = Off_img.resize((btnWidth, btnWidth))
On_BtnImg = ImageTk.PhotoImage(On_resize_img)
Off_BtnImg = ImageTk.PhotoImage(Off_resize_img)
normalWidth = 1920 # Width of monitor screen used to write this code
normalHeight = 1080 # Height of monitor screen used to write this code
percentWidth = rootWidth / (normalWidth / 100)
percentHeight = rootHeight / (normalHeight / 100)
scale = ((percentWidth + percentHeight) / 2) / 100
fontsize = int(14 * scale)
fontsize = 50
fontstyle = tkFont.Font(family='Arial', size=fontsize)
titleFontsize = int(50 * scale)
if titleFontsize < 8:
titleFontsize = 8
TitleFontstyle = tkFont.Font(family="Gothic", size=titleFontsize)
## Labels ##
titleLabel = Label(root, text="MAX5487 DigiPot", font=TitleFontstyle, fg="red", bg="black")
titleLabel.place(relx=0.35, rely=0.05)
powerLabel = Label(root, text="POWER: OFF", font=fontstyle, fg='red', bg='black')
powerLabel.place(relx=0.2, rely=0.65, anchor=N)
directionLabel = Label(root, text="FORWARD", font=fontstyle, fg='#00FF00', bg='black')
directionLabel.place(relx=0.5, rely=0.65 , anchor=N)
powerBool = True
# boolean for led button
powerBtn = Button(root, image=Off_BtnImg, bg='black', bd=0, activebackground='black', highlightthickness = 0, command=PowerFn)
powerBtn.place(relx=0.2, rely=0.35, anchor=N)
directionBool = False
directionBtn = Button(root, image=On_BtnImg, bg='black', bd=0, activebackground='black', highlightthickness = 0, command=DirectionFn)
directionBtn.place(relx=0.5, rely=0.35, anchor=N)
templabel = Label(root, textvariable=temperature, font=fontstyle, fg='white', bg='red', highlightthickness = 0)
templabel.place(relx=0.2, rely=0.8, anchor=N)
humidlabel = Label(root, textvariable=humidity, font=fontstyle, fg='white', bg='red', highlightthickness = 0)
humidlabel.place(relx=0.5, rely=0.8, anchor=N)
# Button for closing
exit_button = Button(root, text="Exit", font=fontstyle, fg='white', bg='red', highlightthickness = 0, command=root.destroy)
exit_button.place(relx=0.5, rely=0.9, anchor=N)
background_thread = Thread(target=tempsensor)
background_thread.start()
root.mainloop()

Python Tkinter, Display Live Data

I want to display live data in a GUI, in tkinter. The data I am getting contains a list of two integers [current, voltage]. I am getting new data every second.
I managed to create a GUI, now I want to know how to display data in GUI Label widgets (python tkinter) and update labels dynamically. Any suggestions please
Here is my code so far:
#data getting is a list eg. [10, 12]
from tkinter import *
import tkinter.font
#main Window using Tk
win = Tk()
win.title("v1.0")
win.geometry('800x480')
win.configure(background='#CD5C5C')
#Labels
voltage = Label(win, text = "voltage")
voltage.place(x=15, y=100)
current = Label(win, text = "current")
current.place(x=15, y=200)
#display measured values
#how to display here !!!
currentValues = Label(win, text = "want to display somewhere like this")
currentValues.place(x=200, y=100)
voltageValues = Label(win, text = "want to display somewhere like this")
voltageValues.place(x=200, y=200)
mainloop()
If you want to graph your live data and want to avoid using other libraries to do that for you, you might find the following to be an enlightening starting point for creating your own graphs. The sample draws a full circle of values when evaluating the math.sin function that comes in the standard library. The code takes into account automatic sampling, resizing, and updating as needed and should be fairly responsive.
#! /usr/bin/env python3
import math
import threading
import time
import tkinter.ttk
import uuid
from tkinter.constants import EW, NSEW, SE
class Application(tkinter.ttk.Frame):
FPS = 10 # frames per second used to update the graph
MARGINS = 10, 10, 10, 10 # internal spacing around the graph
#classmethod
def main(cls):
tkinter.NoDefaultRoot()
root = tkinter.Tk()
root.title('Tkinter Graphing')
# noinspection SpellCheckingInspection
root.minsize(640, 480) # VGA (NTSC)
cls(root).grid(sticky=NSEW)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
def __init__(self, master=None, **kw):
super().__init__(master, **kw)
self.display = tkinter.Canvas(self, background='white')
self.display.bind('<Configure>', self.draw)
self.start = StatefulButton(self, 'Start Graphing', self.start_graph)
self.grip = tkinter.ttk.Sizegrip(self)
self.grid_widgets(padx=5, pady=5)
self.data_source = DataSource()
self.after_idle(self.update_graph, round(1000 / self.FPS))
self.run_graph = None
def grid_widgets(self, **kw):
self.display.grid(row=0, column=0, columnspan=2, sticky=NSEW, **kw)
self.start.grid(row=1, column=0, sticky=EW, **kw)
self.grip.grid(row=1, column=1, sticky=SE)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def start_graph(self):
self.run_graph = True
threading.Thread(target=self.__simulate, daemon=True).start()
return 'Stop Graphing', self.stop_graph
def stop_graph(self):
self.run_graph = False
return 'Clear Graph', self.clear_graph
def clear_graph(self):
self.data_source.clear()
self.reset_display()
return 'Start Graphing', self.start_graph
# def __simulate(self):
# # simulate changing populations
# for population in itertools.count():
# if not self.run_graph:
# break
# self.data_source.append(population, get_max_age(population, 200))
# def __simulate(self):
# # simulate changing ages
# for age in itertools.count(1):
# if not self.run_graph:
# break
# self.data_source.append(age, get_max_age(250_000_000, age))
def __simulate(self):
# draw a sine curve
for x in range(800):
time.sleep(0.01)
if not self.run_graph:
break
self.data_source.append(x, math.sin(x * math.pi / 400))
def update_graph(self, rate, previous_version=None):
if previous_version is None:
self.reset_display()
current_version = self.data_source.version
if current_version != previous_version:
data_source = self.data_source.copy()
self.draw(data_source)
self.after(rate, self.update_graph, rate, current_version)
def reset_display(self):
self.display.delete('data')
self.display.create_line((0, 0, 0, 0), tag='data', fill='black')
def draw(self, data_source):
if not isinstance(data_source, DataSource):
data_source = self.data_source.copy()
if data_source:
self.display.coords('data', *data_source.frame(
self.MARGINS,
self.display.winfo_width(),
self.display.winfo_height(),
True
))
class StatefulButton(tkinter.ttk.Button):
def __init__(self, master, text, command, **kw):
kw.update(text=text, command=self.__do_command)
super().__init__(master, **kw)
self.__command = command
def __do_command(self):
self['text'], self.__command = self.__command()
def new(obj):
kind = type(obj)
return kind.__new__(kind)
def interpolate(x, y, z):
return x * (1 - z) + y * z
def interpolate_array(array, z):
if z <= 0:
return array[0]
if z >= 1:
return array[-1]
share = 1 / (len(array) - 1)
index = int(z / share)
x, y = array[index:index + 2]
return interpolate(x, y, z % share / share)
def sample(array, count):
scale = count - 1
return tuple(interpolate_array(array, z / scale) for z in range(count))
class DataSource:
EMPTY = uuid.uuid4()
def __init__(self):
self.__x = []
self.__y = []
self.__version = self.EMPTY
self.__mutex = threading.Lock()
#property
def version(self):
return self.__version
def copy(self):
instance = new(self)
with self.__mutex:
instance.__x = self.__x.copy()
instance.__y = self.__y.copy()
instance.__version = self.__version
instance.__mutex = threading.Lock()
return instance
def __bool__(self):
return bool(self.__x or self.__y)
def frame(self, margins, width, height, auto_sample=False, timing=False):
if timing:
start = time.perf_counter()
x1, y1, x2, y2 = margins
drawing_width = width - x1 - x2
drawing_height = height - y1 - y2
with self.__mutex:
x_tuple = tuple(self.__x)
y_tuple = tuple(self.__y)
if auto_sample and len(x_tuple) > drawing_width:
x_tuple = sample(x_tuple, drawing_width)
y_tuple = sample(y_tuple, drawing_width)
max_y = max(y_tuple)
x_scaling_factor = max(x_tuple) - min(x_tuple)
y_scaling_factor = max_y - min(y_tuple)
coords = tuple(
coord
for x, y in zip(x_tuple, y_tuple)
for coord in (
round(x1 + drawing_width * x / x_scaling_factor),
round(y1 + drawing_height * (max_y - y) / y_scaling_factor)))
if timing:
# noinspection PyUnboundLocalVariable
print(f'len = {len(coords) >> 1}; '
f'sec = {time.perf_counter() - start:.6f}')
return coords
def append(self, x, y):
with self.__mutex:
self.__x.append(x)
self.__y.append(y)
self.__version = uuid.uuid4()
def clear(self):
with self.__mutex:
self.__x.clear()
self.__y.clear()
self.__version = self.EMPTY
def extend(self, iterable):
with self.__mutex:
for x, y in iterable:
self.__x.append(x)
self.__y.append(y)
self.__version = uuid.uuid4()
if __name__ == '__main__':
Application.main()
You can change label text dynamically:
This is a way using textvariable option with StringVar and .set() method
str_var = tk.StringVar(value="Default")
currentValues= Label(win, textvariable=my_string_var)
currentValues.place(x=200, y=100)
str_var.set("New value")
Another way using simply .configure() method
currentValues = Label(win, text = "default")
currentValues.configure(text="New value")
Finally, to make the UI update without waiting the rest of the loop do an update
win.update()
I figured out a liveplot inspired by a demo, where I used this to plot the realtime current-voltage scan in Keithley2410.
The whole script is below:
import matplotlib.pyplot as plt
import numpy as np
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from tkinter import ttk
x_data, y_data = [], []
class Win(tk.Tk):
def __init__(self):
super().__init__()
self.title('I-V liveplot')
self.geometry('500x450')
# Frame that holds wigets on the left side
left_frame = ttk.Frame(self)
left_frame.pack(side= "left", padx =10, pady = 10) #, fill="y", expand=True
self.fig = plt.figure(figsize=(4, 3.5), dpi=100)
self.ax = self.fig.add_subplot(1,1,1)
self.line, = self.ax.plot([0], [0])
self.ax.set_xlabel('Voltage / V', fontsize = 12)
self.ax.set_ylabel('Current / A', fontsize = 12)
self.fig.tight_layout()
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.toolbar = NavigationToolbar2Tk(self.canvas, self)
self.canvas.get_tk_widget().pack(side= tk.BOTTOM)
voltage_range_label = tk.Label(left_frame, text = "Voltage range")
voltage_range_label.pack(side = "top", padx =10, pady =2)
self.voltage_range = tk.IntVar()
self.voltage_range.set(10)
voltage_range_spinbox = ttk.Spinbox(left_frame, from_=-3e2, to = 5e2, textvariable = self.voltage_range, width=8)
voltage_range_spinbox.pack(side="top", padx =10, pady =5)
voltage_step_label = tk.Label(left_frame, text = "Step")
voltage_step_label.pack(side = "top", padx =10, pady =2)
self.step = tk.IntVar()
self.step.set(1)
step_spinbox = ttk.Spinbox(left_frame, from_=-3e2, to = 5e2, textvariable = self.step, width =9)
step_spinbox.pack(side="top", padx =10, pady =5)
self.start = tk.BooleanVar(value = False)
start_butt = ttk.Button(left_frame, text="Start", command= lambda: self.start.set(True))
start_butt.pack(side='top', padx =10, pady =10)
stop_butt = ttk.Button(left_frame, text="Resume", command=lambda: self.is_paused.set(False))
stop_butt.pack(side="top", padx =10, pady =10)
self.is_paused = tk.BooleanVar() # variable to hold the pause/resume state
restart_butt = ttk.Button(left_frame, text="Pause", command=lambda: self.is_paused.set(True))
restart_butt.pack(side="top", padx =10, pady =10)
def update(self, k=1):
if self.start.get() and not self.is_paused.get():
# quasi For Loop
idx = [i for i in range(0, k, self.step.get())][-1]
x_data.append(idx)
y_data.append(np.sin(idx/5))
self.line.set_data(x_data, y_data)
self.fig.gca().relim()
self.fig.gca().autoscale_view()
self.canvas.draw()
#self.canvas.flush_events()
k += self.step.get()[![enter image description here][2]][2]
if k <= self.voltage_range.get():
self.after(1000, self.update, k)
if __name__ == "__main__":
app = Win()
app.after(1000, app.update)
app.mainloop()
This code works properly and results in an output shown in Graph. I hope it would be helpful.
.
I want to display some live data in a GUI.
I think what you want to do is use the .after() method. The .after() method queues tkinter to run some code after a set time.
For example:
currentValues = Label(win, text = "want to display somewhere like this")
currentValues.place(x=200, y=100)
voltageValues = Label(win, text = "want to display somewhere like this")
voltageValues.place(x=200, y=200)
def live_update():
currentValues['text'] = updated_value
voltageValues['text'] = updated_value
win.after(1000, live_update) # 1000 is equivalent to 1 second (closest you'll get)
live_update() # to start the update loop
1000 units in the after method is the closest you'll get to 1 second exactly.

How to keep the live graph to see only the last 50 data points?

This post is related to my previous question.Updating Label and Live Graph dynamically using Arduino Data in Tkinter My graph is embedded on Tkinter. I was using the same code but I realized that while my live graph is updating dynamically from the serial data coming from the Arduino, the old data points just keeps on adding. I want to see the last 50 data points to be embedded in the live graph and to delete the old data point. I don't know how to manipulate the code to do exactly what I want to achieve. Any suggestions would be appreciated
Here's the code:
#from tkinter import *
import tkinter as tk # proper way to import tkinter
import serial
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
style.use("ggplot")
import threading
class Dee(tk.Frame):
def __init__(self, master=None, title='', ylabel='', label='', color='c', ylim=1, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.data = []
fig = Figure(figsize = (7,6))
self.plot = fig.add_subplot(111)
self.plot.set_title(title)
self.plot.set_ylabel(ylabel)
self.plot.set_ylim(0, ylim)
self.line, = self.plot.plot([], [], color, marker = 'o',label = label)
self.plot.legend(loc='upper left')
label = tk.Label(self, text = ylabel, font = "Times 22 bold")
label.grid(row = 0, column = 3)
button_1 = tk.Button(self, text = "Back To Homepage", command = F1.tkraise)
button_1.grid(row = 1, column = 2)
label_1 = tk.Label(self, text = "Current Value: ", font = "Verdana 10 bold")
label_1.grid(row = 2, column = 2)
self.label_data = tk.Label(self, font = "Verdana 10")
self.label_data.grid(row = 2, column = 3)
canvas = FigureCanvasTkAgg(fig, master=self)
canvas.get_tk_widget().grid(row = 3, column = 3)
ani = animation.FuncAnimation(fig, self.update_graph, interval = 1000, blit = False)
canvas.draw()
def update_graph(self, i):
if self.data:
self.line.set_data(range(len(self.data)), self.data)
self.plot.set_xlim(0, len(self.data))
def set(self, value):
self.data.append(value)
self.label_data.config(text=value)
my_window = tk.Tk()
my_window.title("Graphical User Interface Demo#1")
my_window.geometry("720x720")
F1 = tk.Frame(my_window)
F2 = Dee(my_window, title='Temperature Graph', ylabel='Temperature', color='c', label='Degrees C', ylim=40)
F3 = Dee(my_window, title='Humidity Graph', ylabel='Humidity', color='g', label='Percentage %', ylim=100)
F4 = Dee(my_window, title='Solved Water Graph', ylabel='Water Volume', color='b', label='mL', ylim=55)
#For Frame One
label_1 = tk.Label(F1, text = "Homepage of GUI", font = "Times 22 bold")
label_1.grid(row = 0, column = 3)
button_1 = tk.Button(F1, text = "Page of Humidity", bd = 8, command = F2.tkraise)
button_1.grid(row = 1, column = 2)
button_2 = tk.Button(F1, text = "Page of Temperature", bd = 8, command = F3.tkraise)
button_2.grid(row = 1, column = 3)
button_3 = tk.Button(F1, text = "Page of Water", bd = 8, command = F4.tkraise)
button_3.grid(row = 1, column = 4)
for frame in(F1, F2, F3, F4):
frame.grid(row = 0, column = 0, sticky = "NSEW")
F1.tkraise()
def get_data():
#Initialization of Serial Comm
ser = serial.Serial('COM3', 9600)
while True:
pulldata = ser.readline().decode('ascii')
get_data = pulldata.split(',')
F2.set(int(get_data[0]))
F3.set(int(get_data[1]))
F4.set(int(get_data[2]))
print(pulldata)
# start the thread that will poll the arduino
t = threading.Thread(target=get_data)
t.daemon = True
t.start()
my_window.mainloop()
To select only the last n data from an array use
data[-n:]
To let a plot autoscale use
ax.autoscale()

Regarding Tkinter() Based GUI for Arduino

I'm trying to write a python GUI for an arduino, the idea is that a computer will be communicating through serial port with the arduino.
I figured the arduino code itself was too complex to try controlling the thing directly from like matlab, so instead I'd like to control it by sending commands through serial. In addition to that I'm trying to live plot some data.
I'm having a hard time getting the live plot to work without having hard-coded the connection port into the script.
def connectArduino():
global e1
serial_port = e1.get()
baud_rate = 9600
path = "data.txt"
ser = serial.Serial(serial_port, baud_rate, timeout=0, writeTimeout=0)
def setTunings():
global entries
kp = entries[0][1].get()
ki = entries[1][1].get()
kd = entries[2][1].get()
print(kp + ki + kd)
def animate(i):
ser.reset_input_buffer()
data = ser.readline()
data_array = data.split('\t')
xvalue = float(data_array[0])/1000
yvalue = float(data_array[1])
yar.append(yvalue)
xar.append(xvalue)
line.set_data(xar, yar)
ax1.set_xlim(0, i+1)
print(data_array[0] + '\t' + data_array[1])
import serial
from Tkinter import *
from matplotlib import pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
root = Tk()
#root.geometry('1200x700+200+100')
root.title('Temperature Control')
root.config(background='#fafafa')
frame = Frame(root)
frame.pack(side=LEFT)
l1 = Label(frame, text = "Connection Port:", anchor ='w')
l1.pack()
e1 = Entry(frame)
e1.pack()
b1 = Button(frame, text = "Connect", command = connectArduino)
b1.pack()
fields = 'Kp', 'Ki', 'Kd'
entries = []
for field in fields:
row = Frame(frame)
lab = Label(row, width=15, text=field, anchor='w')
ent = Entry(row)
row.pack(side=TOP, fill=X, padx=5, pady=5)
lab.pack(side=LEFT)
ent.pack(side=RIGHT, expand=YES, fill=X)
entries.append((field, ent))
b2 = Button(frame, text = 'Set Tunings')
b2.pack()
b3 = Button(frame, text = 'Start')
b3.pack(side=LEFT, padx=5, pady=5)
#b2.pack(side = LEFT, padx=5, pady=5)
b4 = Button(frame, text = 'Stop')
b4.pack(side=RIGHT, padx=5, pady=5)
#b3.pack(side = LEFT, padx=5, pady=5)
xar = []
yar = []
style.use('ggplot')
fig = plt.figure(figsize=(8, 8), dpi=100)
ax1 = fig.add_subplot(1, 1, 1)
ax1.set_ylim(0, 35)
line, = ax1.plot(xar, yar, 'r', marker='o')
plotcanvas = FigureCanvasTkAgg(fig, root)
plotcanvas.get_tk_widget().pack(side = RIGHT)
ani = animation.FuncAnimation(fig, animate, interval=100, blit=False)
root.mainloop()
I must admit I'm a lot more used to C and cpp than I am python so a lot of this I've gui stuff I've been reading about has been going over my head a little bit.
TLDR: Trying to write/read from serial prompt as well as displaying some data in a live plot all with a GUI and button/text entry user input.
From your description in a comment:
The error is that animate is calling the "ser" object without it being defined…
Right, ser doesn't exist as a global variable, because the only place you define it is as a local variable, inside connectArduino:
def connectArduino():
global e1
serial_port = e1.get()
baud_rate = 9600
path = "data.txt"
ser = serial.Serial(serial_port, baud_rate, timeout=0, writeTimeout=0)
The solution is simple: just as you added global e1 to make e1 a global variable rather than a local one, you need to add global ser to make ser a global variable rather than a local one.
Then, after you call connectArduino, ser will be defined, so animate will be able to use it.

Updating canvas in Tkinter

Does anybody know how to modify the following code so that the graph is updated every time I change a goniometric function? The plotting is done in function plot(). There are some related threads on stackoverflow, but I was not able to apply them on my example code...
Many thx,
Macky
import numpy as np
import Tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class App:
def __init__(self, window):
self.window = window
# set up pi
pi = 3.141592654
# create the frame and pack to the object
frame = tk.Frame(window)
frame.pack()
def callback(func, *pargs):
print(func.get())
func.set(func.get())
self.plot()
self.window.update()
self.func = tk.StringVar()
self.func.set("sin") # default value
self.func.trace('w', lambda *pargs: callback(self.func, *pargs))
self.button = tk.OptionMenu(window, self.func, "sin", "cos", "tan")
self.button.pack(side = tk.TOP)
self.min_value = tk.Entry(window, justify = tk.RIGHT)
self.min_value.pack()
self.min_value.delete(0, tk.END)
self.min_value.insert(0, -pi)
self.max_value = tk.Entry(window, justify = tk.RIGHT)
self.max_value.pack()
self.max_value.delete(0, tk.END)
self.max_value.insert(0, pi)
self.button = tk.Button(frame, text = "QUIT", foreground = "red", command = frame.quit)
self.button.pack(side = tk.BOTTOM)
self.draw = tk.Button(frame, text = "DRAW", command = self.plot())
self.draw.pack(side = tk.LEFT)
def plot(self):
# generate numbers for the plot
x = np.array(np.arange(np.float64(self.min_value.get()), np.float64(self.max_value.get()), 0.001))
if self.func.get() == 'sin':
print('plotting sin()')
y = np.sin(x)
elif self.func.get() == 'cos':
print('plotting cos()')
y = np.cos(x)
else:
print('plotting tan()')
y = np.tan(x)
# create the plot
fig = Figure(figsize = (6, 6))
a = fig.add_subplot(1,1,1)
a.plot(x, y, color = 'blue')
a.set_title ("Goniometric Functions", fontsize = 12)
a.set_ylabel(self.func.get() + '(x)', fontsize = 8)
a.set_xlabel('x', fontsize = 8)
# canvas
canvas = FigureCanvasTkAgg(fig, master = self.window)
self.widget = canvas.get_tk_widget().pack()
print('here I should update canvas')
canvas.draw()
root = tk.Tk()
app = App(root)
root.mainloop()
root.destroy()
You should create empty plot on canvas and later only replace data in plot using a.set_xdata(), a.set_ydata()
import numpy as np
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class App:
def __init__(self, window):
self.window = window
# set up pi
pi = 3.141592654
# create the frame and pack to the object
frame = tk.Frame(window)
frame.pack()
def callback(func, *pargs):
print(func.get())
#func.set(func.get()) # <-- it has no sense
self.update_plot()
self.window.update()
self.func = tk.StringVar()
self.func.set("sin") # default value
self.func.trace('w', lambda *pargs: callback(self.func, *pargs))
self.button = tk.OptionMenu(window, self.func, "sin", "cos", "tan")
self.button.pack(side = tk.TOP)
self.min_value = tk.Entry(window, justify = tk.RIGHT)
self.min_value.pack()
self.min_value.delete(0, tk.END)
self.min_value.insert(0, -pi)
self.max_value = tk.Entry(window, justify=tk.RIGHT)
self.max_value.pack()
self.max_value.delete(0, tk.END)
self.max_value.insert(0, pi)
self.button = tk.Button(frame, text="QUIT", foreground="red", command=root.destroy) #<--
self.button.pack(side = tk.BOTTOM)
self.draw = tk.Button(frame, text="DRAW", command=self.update_plot)
self.draw.pack(side = tk.LEFT)
# create empty plot
self.create_plot()
# update plot with current function at start
self.update_plot()
def create_plot(self):
# create the plot
self.fig = Figure(figsize = (6, 6))
self.a = self.fig.add_subplot(1,1,1)
self.p, _ = self.a.plot([], [], color = 'blue')
self.a.set_title ("Goniometric Functions", fontsize = 12)
# canvas
self.canvas = FigureCanvasTkAgg(self.fig, master = self.window)
self.widget = self.canvas.get_tk_widget().pack()
#self.canvas.draw()
def update_plot(self):
print('update')
# generate numbers for the plot
x = np.array(np.arange(np.float64(self.min_value.get()), np.float64(self.max_value.get()), 0.001))
if self.func.get() == 'sin':
print('plotting sin()')
y = np.sin(x)
elif self.func.get() == 'cos':
print('plotting cos()')
y = np.cos(x)
else:
print('plotting tan()')
y = np.tan(x)
# replace labels
self.a.set_ylabel(self.func.get() + '(x)', fontsize = 8)
self.a.set_xlabel('x', fontsize = 8)
# replace data
self.p.set_xdata(x)
self.p.set_ydata(y)
# rescale
self.a.relim()
self.a.autoscale_view()
# update screen
self.canvas.draw()
root = tk.Tk()
app = App(root)
root.mainloop()
#root.destroy() You don't need it
#furas - Thx a lot for the post. Figured out based on your comment 10 minutes after your post. Although being second to you, I am sending my version of the code as well ;). I think, we can close this thread as solved...
Macky
import numpy as np
import Tkinter as tk
import matplotlib.figure as fg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class App:
def __init__(self, window):
self.window = window
# set up pi
pi = 3.141592654
# create empty canvas
x = []
y = []
fig = fg.Figure(figsize = (4,4))
fig_subplot = fig.add_subplot(111)
self.line, = fig_subplot.plot(x, y)
self.canvas = FigureCanvasTkAgg(fig, master = self.window)
self.canvas.show()
self.canvas.get_tk_widget().pack(side = tk.TOP, fill = tk.BOTH, expand = 1)
# create the frame and pack to the object
frame = tk.Frame(window)
frame.pack()
def callback(func, *pargs):
print(func.get())
func.set(func.get())
self.plot()
self.func = tk.StringVar()
self.func.set("sin") # default value
self.func.trace('w', lambda *pargs: callback(self.func, *pargs))
self.button = tk.OptionMenu(window, self.func, "sin", "cos", "tan")
self.button.pack(side = tk.TOP)
self.min_value = tk.Entry(window, justify = tk.RIGHT)
self.min_value.pack()
self.min_value.delete(0, tk.END)
self.min_value.insert(0, -pi)
self.max_value = tk.Entry(window, justify = tk.RIGHT)
self.max_value.pack()
self.max_value.delete(0, tk.END)
self.max_value.insert(0, pi)
self.button = tk.Button(frame, text = "QUIT", foreground = "red", command = frame.quit)
self.button.pack(side = tk.BOTTOM)
def plot(self):
# generate numbers for the plot
x = np.array(np.arange(np.float64(self.min_value.get()), np.float64(self.max_value.get()), 0.001))
if self.func.get() == 'sin':
print('plotting sin()')
y = np.sin(x)
elif self.func.get() == 'cos':
print('plotting cos()')
y = np.cos(x)
else:
print('plotting tan()')
y = np.tan(x)
# update canvas
self.line.set_data(x, y)
ax = self.canvas.figure.axes[0]
ax.set_xlim(x.min(), x.max())
ax.set_ylim(y.min(), y.max())
self.canvas.draw()
root = tk.Tk()
app = App(root)
root.mainloop()
root.destroy()

Categories