I am trying to write a Python GUI and I need to do a live plot. I currently have a program that receives data from a machine I am using and I want to be able to plot the values the machine outputs as I receive them. I have been researching and from what I have found so far, it doesn't seem to me like tkinter or any library can do this in a GUI. Does anyone know whether and how tkinter can do this or if there is another library that is capable of doing such a live plot?
Also, how would I go about writing the data that I gather to a file as I receive the data?
Thanks in advance for your help.
It looks like you get the data by polling, which means you don't need threads or multiple processes. Simply poll the device at your preferred interface and plot a single point.
Here's an example with some simulated data to illustrate the general idea. It updates the screen every 100ms.
import Tkinter as tk
import random
class ServoDrive(object):
# simulate values
def getVelocity(self): return random.randint(0,50)
def getTorque(self): return random.randint(50,100)
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.servo = ServoDrive()
self.canvas = tk.Canvas(self, background="black")
self.canvas.pack(side="top", fill="both", expand=True)
# create lines for velocity and torque
self.velocity_line = self.canvas.create_line(0,0,0,0, fill="red")
self.torque_line = self.canvas.create_line(0,0,0,0, fill="blue")
# start the update process
self.update_plot()
def update_plot(self):
v = self.servo.getVelocity()
t = self.servo.getTorque()
self.add_point(self.velocity_line, v)
self.add_point(self.torque_line, t)
self.canvas.xview_moveto(1.0)
self.after(100, self.update_plot)
def add_point(self, line, y):
coords = self.canvas.coords(line)
x = coords[-2] + 1
coords.append(x)
coords.append(y)
coords = coords[-200:] # keep # of points to a manageable size
self.canvas.coords(line, *coords)
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
As far as I understand it, it can be done with tkinter/Qt/wxpython etc. You just have to make use of mutlithreading and multiprocessing.
There may be a simpler way to do it with another module, but I am unaware of it.
I have been looking into something similar to this problem for a long time as well, and it appears that it is a constant issue among this community.
Here are some threads which talk about the issue:
How do I refresh a matplotlib plot in a Tkinter window?
How do I update a matplotlib figure while fitting a function?
Related
I've made a script that uses a while True loop to constantly update a series of variables based on UDP packets that I am constantly recieving. I want to ultimately create a GUI that displays that data and updates the screen constantly, which I plan to do with tkinter (using my_label.after in a function which then calls itself, not sure if this is a good plan).
Here is some testing scripts that I can't get to work properly:
GUI2.py (my test looping script)
import time
var = 0
while True:
var += 1
time.sleep(0.1)
GUI Testing.py (the script that would be accessing those variables)
from GUI2 import *
import time
print('never')
print(var)
time.sleep(1)
The second script never reaches the print('never') line, I think because it gets stuck in the other script's while True loop and never returns.
How should I go about this? I have one script that I want in a constant loop to update my variables to the correct values based on incoming packets, and then another script updating a tkinter window. I went this way as most examples I could find using Tkinter didn't use any sort of while True loops. Could I just put my packet recieving code inside the Tkinter mainloop, and would that effectively act as a while True?
EDIT (added Tkinter loop that I can't get working):
This opens a Tkinter window, but the label stays at 99, then reopens a window when I close it with the new x value (ie. 98, 97, etc). I want the label to update every second.
import tkinter as tk
import time
x = 99
while True:
root = tk.Tk()
label = tk.Label(root, text=x)
label.pack()
x -= 1
time.sleep(1)
root.mainloop()
Below is a sample script to show you how you can update the value in the label widget at a certain time interval. I have provided you the hyperlinks to help you understand tkinter's methods. Best regards.
Key points:
use the textvariable option of the tk.Label widget.
use tkinter's control variable. I have shown you how to set and get it's value.
you can use tkinter's widget method called .after() without having to explicitly use a while-statement and time.sleep() method. Tkinter has it's own event loop that you can use.
writing your tkinter GUI as a class makes it easier to implement what you need.
Example Script:
import tkinter as tk
class App(tk.Frame):
def __init__( self, master, *args, **kw ):
super().__init__( master )
self.master = master
self.create_label()
self.update_label()
def create_label( self ):
self.var = tk.IntVar() # Holds an int; default value 0
self.label = tk.Label(self, textvariable=self.var ) # Use textvariable not text
self.label.pack()
def update_label( self ):
value = self.get_value()
self.var.set( value ) # Set the label widget textvariable value.
self.after(1000, self.update_label) # Call this method after 1000 ms.
def get_value( self ):
'''To simulate calling a function to return a value'''
value = self.var.get() + 1
return value
if __name__ == "__main__":
root = tk.Tk()
root.geometry('100x100+0+24')
app = App( root )
app.pack()
root.mainloop() #This command activates tkinter's event loop
Edit:
As a clarification, this answer shows how to utilize the .after() and .mainloop() methods in GUI Testing.py, i.e. using tkinter event loop and not use two while-loops, to achieve what you wanted to do. This is a way to simplify your GUI script.
For more sophisticated algorithms, e.g. more than one while-loop is involved, you have to look into using threads(note it has its issues) or more recently I found a way of using python's Asyncio approach to do it. The learning curve for these two approaches is a lot steeper. To use the asyncio approach, you can explore modifying my answer to do what you want.
Best solution is to use threads however If you plan to do in simplest possible manner then implement the main loop inside your Tkinter GUI and once you read the packet simply update it on your GUI in same loop. Here is the Updated and working Code.
import tkinter as tk
import time
def setvalue(self, x):
self.label.config(text=x, )
root.update()
time.sleep(1)
def changevalues(self):
x = 99
self.label = tk.Label(root, text=x)
self.label.pack()
while x >0:
x -= 1
setvalue(root,x)
root = tk.Tk()
changevalues(root)
root.mainloop()
I am having problem with having a GUI interface to be implemented via a second file which just contains the file to read, plots made and some new functions to be evaluated based on that.
I am trying to create a GUI application using Tkinter. The way I am doing is as follows. I have a background script (say Background.py) which has two functions. Function X loads a data file, does some calculations and outputs a graph. The way I want to trigger this is via a GUI script in another file (GUI.py) which opens a panel with a button and when I click the button the function X in file Background.py should be evaluated and a plot should be shown. Once I check the plot, I can hit another button to close the plot and terminate the function X. Now I can choose to click another button to trigger the function Y in the file Background.py. These button should allow me to input three values, which should be the input to the function Y in the file Background.py. Once I hit this button, it should trigger the function Y and do what it it asks it to do. Now at the end, after that I can hit the button to close the gui.
How can I do this?. A general rough idea will be helpful.
I have put an example as much as I can:( at least the skeleton of the code)
I have a background file say (Background.py) and gui file ( say GUI.py)
Background.py
import numpy
import matplotlib.pyplot as plt
import pandas
def progX():
df = pd.read (myfile)
##df.stats # doing something and generating a plot from the file
plt.boxplot(df['col'])
plt.show()
def progY(y1, y2,y3):
## get the y1, y2, y3 from the GUI interface which the user has entered
#run a code... and generate an output file
GUI.py
import Background as bg
from tkinter import *
from tkinter.ttk import *
class GUI ():
def create widgets(self):
#....
def create_panel2(self):
#create buttons
panel1 = ...
btn1 = Button(panel1, text="yyyyy", command=bg.progA)
btn1.pack()
def create_panel2(self):
#create buttons
panel2 = ...
btn2 = Button(panel1, text="yyyyy", command=bg.progB)
btn2.pack()
All_Entries = []
window = Tk()
D=GUI(window)
window.mainloop()
runprogram1 = bg.progX()
runprogram2 = bg.probY(x, y, z)
My question is now, does the above makes sense? How can I call the background functions from the GUI? The statements runprogram1 & runprogram2 are definitely not correct, How can I implement that. Also how will I ensure that I call the proram Y in Background once I have close the output from the program X?
I guess the questions makes sense. I am new to GUI and having hard time working this out, which I need to. any help will be very much appreciated.
I'm assuming progA == progX, and progB == progY?
As your code is currently structured, some function in the GUI needs to get y1, y2 and y3 from a widget (Entry(), presumably), and pass to progY. progY can't fetch that info, b/c progY isn't aware of the widgets in the GUI. Get those values in the GUI class by binding the button to another function that 1) calls .get() on the Entry() widget, and then 2) passes those values to progY.
Make your Entry boxes in the GUI:
e1 = Entry(panel1)
e2 = Entry(panel1)
e3 = Entry(panel1)
self.entries = (e1, e2, e3)
for e in self.entries:
e.pack()
Make a function that gets values and calls progY in the GUI:
def get_entries_call_y(self):
e = [x.get() for x in self.entries]
bd.progY(e[0], e[1], e[2])
Bind your button to get_entries_call_y (not to bd.progY):
btn2 = Button(panel1, text="yyyyy", command=get_entries_call_y)
If you'd like advice on structuring a GUI program in general, try adhering (as best you can) to a standard user interface architecture like model-view-controller (https://en.wikipedia.org/wiki/Model-view-controller). The solution I described above should make your program work, but the way you've structured your program isn't really a good way to do it for a lot of reasons. Learning MVC will help you organize what task should go into what function/class/file, give you a logical framework for adding features and improving your code, and allow you to create new GUI programs more efficiently.
Or at least that's what I got out of learning MVC.
I have a general problem, which I sketch here as the details will be too involved to post here. I know the problem statement, is a bit fuzzy and I may need to go back and forth with an expert in this site. (unfortunately, it is difficult to put everything up here based on the type of the problem. I will really appreciate any help).
I am trying to create a GUI application using Tkinter. The way I am doing is as follows. I have a background script (say back.py) which has the data loaded, calculations done and graph plotted.
Now the way I want to do is that I have a GUI script which uses Tkinter and calls the back.py ( using import). Now I have the window created, with a button. This is where I am stuck. I want to click the button to trigger the background script and generate the plots ( which the background script generates).
After this I want to close the plot and want my GUI to pop up some buttons to input me some parameters. These parameters will be input to the next part of the back.py code ( I chose the parameters based on the plot). When I again click the button ( with the parameters selected), I want to start running the background code again which will output me a file.
How can I do this?. A general rough idea will be helpful.
Let me put an example as much as I can:( at least the skeleton of the code)
I have a background file say (a.py) and gui file ( say g.py)
a.py
import ...
def progA():
# reading a file
# doing something and generating a plot from the file
# Once the GUI's first part is done generating the plot, I need to close that
# plot (or button) and then click the button 2 to run the next function
def progB(y1, y2,y3):
#run a code... and generate an output file
g.py
from tkinter import *
from tkinter.ttk import *
class GUI ():
def create widgets(self):
#....
def create panel(self):
#create buttons
panel1 = ...
btn1 = Button(panel1, text="yyyyy", command=progA)
btn1.pack()
def create_panel1(self):
#create buttons
panel1 = ...
btn1 = Button(panel1, text="yyyyy", command=progA)
btn1.pack()
def create_panel2(self):
#create buttons
panel2 = ...
btn2 = Button(panel1, text="yyyyy", command=progB)
btn2.pack()
All_Entries = []
window = Tk()
D=GUI(window)
window.mainloop()
import a
runprogram1 = a.progA()
runprogram2 = a.probB(x, y, z)
My question is now, does the above makes sense? So I have a couple of questions:
How will I ensure that when I close the plots (from the output of progA), that the second button will show up?
Where can I input the there values of the parameters in the second button?
I have a GUI in Tkinter that is becoming more complicated. I would like to separate it into some modules to make it easier to manage. Is there a way to separate my GUI into modules if I didn't use the object oriented approach?
Here is a simple example of some code I would like to move to a separate module:
def create_main_nav_buttons(strat_folder_list):
global dynamic_info_nav_items
temp_count = 0
for item in strat_folder_list:
main_nav = tk.Canvas(Nav_Frame_Top, width=175, height=grid_box_size/1.5, highlightthickness=1, bg='slategray1')
main_nav.grid(row = temp_count, column = 1)
main_nav.bind("<Button-1>", lambda event, x=item: create_navigation2(x))
temp_count += 1
dynamic_info_nav_items.append(main_nav)
-Side note:
I wrote a GUI using the object oriented approach before but decided to not use it this time because I didn't fully understand parts of it such as:
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
So when something went wrong it was a nightmare to fix and I couldn't find much support.
Using OO techniques is the proper way to divide code into modules, but it's not the only way. I think it's a bit more work to do it without classes, but there's nothing preventing you from doing what you want without classes.
The main thing you need to change about the way you are coding is to stop relying on global variables. That means you need to pass in information from the main program to the functions, and the functions need to return information back to the main program.
Example
I've tried to keep the code in this example as close to your original as possible, though I've made a few small changes.
First, create a file named "widgets.py" with the following contents:
import tkinter as tk
def create_main_nav_buttons(parent, grid_box_size, items):
temp_count = 0
widgets = []
for item in items:
main_nav = tk.Canvas(parent, width=175, height=grid_box_size/1.5, highlightthickness=1, bg='slategray1')
main_nav.grid(row = temp_count, column = 1)
main_nav.bind("<Button-1>", lambda event, x=item: create_navigation2(x))
temp_count += 1
widgets.append(main_nav)
return widgets
def create_navigation2(x):
print("create_navigation2...", x)
Next, create your main program in a file named "main.py":
import tkinter as tk
from widgets import create_main_nav_buttons
root = tk.Tk()
Nav_Frame_Top = tk.Frame(root)
Nav_Frame_Top.pack(side="top", fill="x")
dynamic_info_nav_items = []
strat_folder_list = ["one", "two", "three"]
grid_box_size=10
widgets = create_main_nav_buttons(Nav_Frame_Top, grid_box_size, strat_folder_list)
dynamic_info_nav_items += widgets
root.mainloop()
Notice that the parent and the other items that you were expecting to be in the global namespace are passed in to the function, and then the function returns values which the main program can use.
In essence you are creating a contract between the function and the main program. The contract is "you tell me the information I need to construct the widgets, and I'll create the widgets and return them to you in a list".
For fun, I'm creating a crappy helicopter/flappybird clone using tkinter and I've run into some really bizarre behavior with regards images apparently not showing up.
(btw, using python3)
So I started with the following code just to see if I could start getting things to draw:
from tkinter import *
from PIL import ImageTk
class Bird(object):
def __init__(self, canvas, x=0, y=0):
self.canvas = canvas
photo = ImageTk.PhotoImage(file="flappy.gif")
self.bird = self.canvas.create_image(x,y,image=photo)
class Environment(Canvas):
def __init__(self, master, width=500, height=500):
super(Environment, self).__init__(master, width=width, height=height)
self.pack()
self.master = master
self.bird = Bird(self)
if __name__=="__main__":
r = Tk()
env = Environment(r)
env.pack()
r.mainloop()
Image didn't appear, all I had was a blank canvas. I thought this was odd, so I started playing around to see why that might be the case. My next step was to test that I knew how to create images, so I just made my file a basic image create:
if __name__=="__main__":
r,c=get_canv()
c.pack()
img = ImageTk.PhotoImage(file="flappy.gif")
c.create_image(100,100,image=img)
r.mainloop()
And this, predictably, works fine. So, my syntax in the prior code seemed to be correct. This is when I stumbled on something a little confusing:
if __name__=="__main__":
r,c=get_canv()
c.pack()
c.create_image(100,100,image=ImageTk.PhotoImage(file="flappy.gif"))
r.mainloop()
This didn't draw. I'm left with a blank canvas again. This is what made me suspect that maybe there was some weird threading issue going on behind the scenes. Does Anyone know why the second snippet worked and the third snippet failed?
I've seen this a number of times already. The problem is that the PhotoImage is garbage collected even though it is used in the Label! To fix the problem, just bind it to a member variable of the GUI itself:
self.photo = ImageTk.PhotoImage(file="flappy.gif")
self.bird = self.canvas.create_image(x,y,image=self.photo)
The reason it works in your second example is that the img variable exists until after the mainloop method has finished, while in your third example, it exists only during the creation of the Label.