Update Matplotlib plot in Tkinter frame - python

I'm trying to plot data from serial port in a tkinter gui. The plot should be plotted and updated only when a specific pack comes from serial.
I can parse the new incoming data and update the GUI (text area). But when I call the "plot()" function from the "update_gui" thread, the program quit and I get the
"Process finished with exit code -1073741819 (0xC0000005)"
message.
Instead, if I call "plot()" from somewhere else (command button, or simply before mainloop()), the plot is generated and shown.
Here is the relevant part of the code:
import threading
import tkinter as tk
import tkinter.scrolledtext as st
import rx_seriale as rx_ser
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
import queue
running = True
serial_data = ''
filter_data = ''
update_period = 5
serial_object = None
gui = tk.Tk()
gui.title("UART Interface")
my_queue = queue.Queue()
t1 = ''
def plot(valori):
global frame_plot1
try:
# the figure that will contain the plot
fig = Figure(figsize=(4.5, 4), dpi=100)
# adding the subplot
plot1 = fig.add_subplot(111)
#dummy values
y = [i ** 2 for i in range(101)]
# plotting the graph
plot1.plot(y)
# creating the Tkinter canvas
# containing the Matplotlib figure
canvas = FigureCanvasTkAgg(fig, master=frame_plot1)
canvas.draw()
# placing the canvas on the Tkinter window
canvas.get_tk_widget().pack()
# creating the Matplotlib toolbar
#toolbar = NavigationToolbar2Tk(canvas, frame_plot1)
#toolbar.update()
# placing the toolbar on the Tkinter window
#canvas.get_tk_widget().pack()
except Exception as e:
print('Errore:' + str(e))
def update_gui():
global filter_data
global update_period
global my_queue
global type_test, test_status,flag
while (1):
data = my_queue.get(block=True)
text.insert('end', test_status[data[0]] + " - " + type_test[data[1]])
text.insert('end', '\n')
text.see('end')
if (data[1] == 6):
plot(1)
if __name__ == "__main__":
'''
...
all the stuff for design TK windows
...
'''
# threads
t2 = threading.Thread(target=update_gui)
t2.daemon = True
t2.start()
# mainloop
gui.geometry('1000x500')
gui.mainloop()
What goes wrong? Thank you.

Related

Matplotlib FuncAnimation fails to run and is bypassed in tkinter GUI

I am trying to have two daemonic threads run alongside my tkinter main thread. One of the threads generates data and the other thread runs FuncAnimation that plots the data that is generated. However, I am having issues with getting FuncAnimation to run, I can see that whenever I click the button both print statements are shown in the console meaning FuncAnimation appears to have been skipped or ran but exited due to some missing or bad parameter. I checked the matplotlib documentation to ensure I'm not missing anything and I think I have created all the objects and called the right methods to make this happen but something is obviously amiss. Any help is greatly appreciated.
import tkinter as tk
import time
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
from matplotlib import animation
import threading
root = tk.Tk()
class dynamicChart():
def __init__(self):
self.x = []
self.y = []
self.buildChart()
def buildChart(self):
self.figure = Figure(figsize=(5, 5), dpi=100)
ax = self.figure.add_subplot(111)
self.scatter = ax.scatter(self.x,self.y)
canvas = FigureCanvasTkAgg(self.figure, root)
canvas.draw()
canvas.get_tk_widget().pack(pady=10)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas.get_tk_widget().pack()
def animate(self,i):
print("Enter animate")
self.scatter.set_offsets([self.x, self.y])
def Ani(self):
print("Enter Ani")
self.animation = animation.FuncAnimation(self.figure, self.animate, interval = 2000)
print("Exit Ani")
class genData():
def __init__(self):
self.chart = dynamicChart()
def generate(self):
X = 2
Y = 2
i = 1
while True:
time.sleep(1)
X = X*i
Y = Y + i
self.chart.x.append(X)
self.chart.y.append(Y)
i+=1
StartDataGen_CreateFigureAxes = genData()
genData = threading.Thread(target = StartDataGen_CreateFigureAxes.generate, daemon = True)
genData.start()
ani = threading.Thread(target = StartDataGen_CreateFigureAxes.chart.Ani, daemon = True)
button = tk.Button(root, text="Graph It", command = lambda: ani.start())
button.pack()
root.mainloop()

Why does Python Tkinter Canvas draw causes a freeze? [duplicate]

I have written a piece of code where I have a simple GUI with an canvas. On this canvas I draw a Matplot. The Matplot is updated every second with data from an SQ Lite DB which I fill with some fake Sensor information (just for testing at the moment).
My Problem was that the redrawing of the canvas causes my window/gui to lag every second. I even tried to update the plot in another thread. But even there I get an lag.
With my newest Code i got most of my things working. Threading helps to prevent my GUI/Window from freezing while the Canvas is updated.
The last thing I miss is to make it Thread safe.
This is the message I get:
RuntimeError: main thread is not in main loop
Here is my newest working code with threading:
from tkinter import *
import random
from random import randint
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import time
import threading
from datetime import datetime
continuePlotting = False
def change_state():
global continuePlotting
if continuePlotting == True:
continuePlotting = False
else:
continuePlotting = True
def data_points():
yList = []
for x in range (0, 20):
yList.append(random.randint(0, 100))
return yList
def app():
# initialise a window and creating the GUI
root = Tk()
root.config(background='white')
root.geometry("1000x700")
lab = Label(root, text="Live Plotting", bg = 'white').pack()
fig = Figure()
ax = fig.add_subplot(111)
ax.set_ylim(0,100)
ax.set_xlim(1,30)
ax.grid()
graph = FigureCanvasTkAgg(fig, master=root)
graph.get_tk_widget().pack(side="top",fill='both',expand=True)
# Updated the Canvas
def plotter():
while continuePlotting:
ax.cla()
ax.grid()
ax.set_ylim(0,100)
ax.set_xlim(1,20)
dpts = data_points()
ax.plot(range(20), dpts, marker='o', color='orange')
graph.draw()
time.sleep(1)
def gui_handler():
change_state()
threading.Thread(target=plotter).start()
b = Button(root, text="Start/Stop", command=gui_handler, bg="red", fg="white")
b.pack()
root.mainloop()
if __name__ == '__main__':
app()
Here the idea without a thread:
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as tk
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import sqlite3
from datetime import datetime
from random import randint
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
root.update_idletasks()
f = Figure(figsize=(5,5), dpi=100)
x=1
ax = f.add_subplot(111)
line = ax.plot(x, np.sin(x))
def animate(i):
# Open Database
conn = sqlite3.connect('Sensor_Data.db')
c = conn.cursor()
# Create some fake Sensor Data
NowIs = datetime.now()
Temperature = randint(0, 100)
Humidity = randint(0, 100)
# Add Data to the Database
c = conn.cursor()
# Insert a row of data
c.execute("insert into Sensor_Stream_1 (Date, Temperature, Humidity) values (?, ?, ?)",
(NowIs, Temperature, Humidity))
# Save (commit) the changes
conn.commit()
# Select Data from the Database
c.execute("SELECT Temperature FROM Sensor_Stream_1 LIMIT 10 OFFSET (SELECT COUNT(*) FROM Sensor_Stream_1)-10")
# Gives a list of all temperature values
x = 1
Temperatures = []
for record in c.fetchall():
Temperatures.append(str(x)+','+str(record[0]))
x+=1
# Setting up the Plot with X and Y Values
xList = []
yList = []
for eachLine in Temperatures:
if len(eachLine) > 1:
x, y = eachLine.split(',')
xList.append(int(x))
yList.append(int(y))
ax.clear()
ax.plot(xList, yList)
ax.set_ylim(0,100)
ax.set_xlim(1,10)
ax.grid(b=None, which='major', axis='both', **kwargs)
label = tk.Label(root,text="Temperature / Humidity").pack(side="top", fill="both", expand=True)
canvas = FigureCanvasTkAgg(f, master=root)
canvas.get_tk_widget().pack(side="left", fill="both", expand=True)
root.ani = animation.FuncAnimation(f, animate, interval=1000)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Here is my DB Schema:
CREATE TABLE `Sensor_Stream_1` (
`Date` TEXT,
`Temperature` INTEGER,
`Humidity` INTEGER
);
Your GUI process must not run in any thread. only the dataacquisition must be threaded.
When needed , the data acquired are transfered to the gui process (or the gui process notified from new data available) . I may need to use a mutex to share data resource between acquisition thread and gui (when copying)
the mainloop will look like :
running = True
while running:
root.update()
if data_available:
copydata_to_gui()
root.quit()
I had the same problem with tkinter and using pypubsub events was my solution.
As comments above suggested, you have to run your calculation in another thread, then send it to the gui thread.
import time
import tkinter as tk
import threading
from pubsub import pub
lock = threading.Lock()
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.label = tk.Label(root, text="Temperature / Humidity")
self.label.pack(side="top", fill="both", expand=True)
def listener(self, plot_data):
with lock:
"""do your plot drawing things here"""
self.label.configure(text=plot_data)
class WorkerThread(threading.Thread):
def __init__(self):
super(WorkerThread, self).__init__()
self.daemon = True # do not keep thread after app exit
self._stop = False
def run(self):
"""calculate your plot data here"""
for i in range(100):
if self._stop:
break
time.sleep(1)
pub.sendMessage('listener', text=str(i))
if __name__ == "__main__":
root = tk.Tk()
root.wm_geometry("320x240+100+100")
main = MainApplication(root)
main.pack(side="top", fill="both", expand=True)
pub.subscribe(main.listener, 'listener')
wt = WorkerThread()
wt.start()
root.mainloop()
This function is called every second, and it is outside the normal refresh.
def start(self,parent):
self.close=False
self.Refresh(parent)
def Refresh(self,parent):
'''your code'''
if(self.close == False):
frame.after( UpdateDelay*1000, self.Refresh, parent)
The function is called alone, and everything that happens inside it does not block the normal operation of the interface.

Tkinter: Moving a Gif on a canvas without PIL

I have a issue in tkinter for python 3. I would like to create an animating game character in python, tkinter without using PIL. I found a way to animate the character using a gif, but I do not know how to move the gif I tried to use canvas.move
here is my code:
from tkinter import *
import os
import time
root = Tk()
c = Canvas(root,width = 500,height = 500)
c.pack()
frames = [PhotoImage(file=(os.path.expanduser("~/Desktop/DaQueenIDLE.gif")),format = 'gif -index %i' % (i)) for i in range(2)]
def update(ind):
frame = frames[ind]
ind += 1
if ind >= 2:
ind = 0
label.configure(image=frame)
root.after(100, update, ind)
label = Label(root)
label.pack()
root.after(0, update, 0)
c.move(frames,0,-100)
root.update()
root.mainloop()
move is a method for Canvas, and its first argument needs to be an item on Canvas.
In your case frames is not an item on the Canvas.
Replace:
def update(ind):
#...
label.configure(image=frame)
root.after(100, update, ind)
label = Label(root)
label.pack()
with:
def update(ind):
#...
c.itemconfig(character, image=frame)
c.move(character, 1, 1)
root.after(100, update, ind)
character = c.create_image((47,47), image=frames[0])
To convert your label into an image item in Canvas and move it.
Example
Below is a complete example that downloads(you can comment download_images out after the initial run) .gif images below online:
and then moves an image while animating between the two:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
def download_images():
# In order to fetch the image online
try:
import urllib.request as url
except ImportError:
import urllib as url
url.urlretrieve("https://i.stack.imgur.com/57uJJ.gif", "13.gif")
url.urlretrieve("https://i.stack.imgur.com/8LThi.gif", "8.gif")
def animate_and_move(i):
i = (i + 1) % 2
canvas.itemconfig(moving_image, image=canvas.images[i])
canvas.move(moving_image, 1, 1)
canvas.after(100, animate_and_move, i)
if __name__ == '__main__':
download_images() # comment out after initial run
root = tk.Tk()
canvas = tk.Canvas(root, height=644, width=644, bg='#ffffff')
canvas.images = list()
canvas.images.append(tk.PhotoImage(file="8.gif"))
canvas.images.append(tk.PhotoImage(file="13.gif"))
moving_image = canvas.create_image((164, 164), image=canvas.images[0])
animate_and_move(0)
canvas.pack()
root.mainloop()
Note that if:
import tkinter
tkinter.TkVersion >= 8.6
returns True then .png files are also supported without an additional library.

How to uncheck a checkbox after the close_event of figure in Python

This is my first question, so please let me know if I make any faux pas.
I'd like to have a check-box that creates and destroys a plot. If that plot is closed using the window's "x", then I'd like the check-box to be unchecked.
I've got it all working except the unchecking upon figure closing.
It looks like my close function is getting called, but it crashes when I try to manipulate the check-box.
from tkFont import *
from Tkinter import *
import matplotlib.pyplot as plt
import numpy as np
class Application(Frame):
def velocity_cb(self):
if self.velocity_var.get()==1:
print "selected Velocity checkbox"
x = np.arange(100.)
self.figureHandles[1] = plt.figure("Velocity")
# set the close event to run my function
self.figureHandles[1].canvas.mpl_connect('close_event', self.handle_close)
plt.plot(x,x)
plt.show()
else:
print "deselected"
plt.close(self.figureHandles[1])
def handle_close(self,event):
#print self.ground_var.get()
print "X'd velocity window"
#print self.figureHandles[1]
#plt.close(self.figureHandles[1])
self.velocity_var.set(0)
def createWidgets(self):
self.figureHandles = [0]*13
self.velocity_var = IntVar()
self.velocity = Checkbutton(self, text="Velocity", variable=self.velocity_var, command=self.velocity_cb)
self.velocity.grid(row=6,column=0,sticky='W')
self.velocity.var = self.velocity_var
def __init__(self,master=None):
Frame.__init__(self,master)
self.pack()
self.createWidgets()
if __name__=='__main__':
root = Tk()
app = Application(master = root)
app.mainloop()

Python: Tkinter and pyplot to replot data

I am trying to write a simple program that reads the values of three different widgets and plots a graph of a function depending on what the input values are. I run into the following problems:
1) On the button press I get the error "App_Window instance has no attribute 'refreshFigure'"
2)On exiting ipython (I use ipython-listener in conjunction with a gedit plugin) I get the error "Exception RuntimeError: 'main thread is not in main loop' in del of
I've based this mostly on this thread - Interactive plot based on Tkinter and matplotlib and this thread - How do I refresh a matplotlib plot in a Tkinter window?
#!/usr/bin/python
from Tkinter import Tk, Frame
from Tkinter import *
import Tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
#this bit I added just because the NVC13 module is in another directory
import sys
for i in range(len(sys.path)):
if sys.path[i]!='/home/captain-pants/NV_centri':
if i==len(sys.path)-1:
sys.path.append('/home/captain-pants/NV_centri')
continue
else:
break
import multiprocessing as mp
import numpy as np
import matplotlib.pyplot as plt
#This module contains all the code necessary to return a list of values that depend on the input parameters
import NVC13_1lev_advanced_ex_nocoh as C13
from matplotlib.figure import Figure
#this function takes a list of parameters and returns a list of values dependant on those parameters.
def plotfunc(c13,Eg,Bz):
Epam=[]
nv = C13.NVC13(c13,[2580,1423],[Eg,Eg],0,0,Bz,0)
evals = nv.intH()
for i in range(len(evals[0])):
Epam.append(np.real_if_close(evals[0][i]))
return Epam
class App_Window:
def __init__(self, parent):
frame = Tkinter.Frame(parent)
self.Egdata=DoubleVar()
self.c13input=IntVar()
self.Bzdata=DoubleVar()
parent.title("Simple")
Tkinter.Button(text='lulz',command=self.main1).grid(row=3,column=0)
Tkinter.Label(textvariable=self.Egdata).grid(row=5,column=0)
self.c13 = Tkinter.Entry(textvariable=self.c13input)
self.Eg = Tkinter.Scale(orient='horizontal')
self.Bz = Tkinter.Scale(orient='horizontal')
self.c13.grid(row=6,column=0)
self.Bz.grid(row=5,column=0)
self.Eg.grid(row=4,column=0)
Fig = Figure()
FigSubPlot = Fig.add_subplot(111)
self.line, = FigSubPlot.plot(range(10),'bo')
x=[]
y=[]
self.canvas = FigureCanvasTkAgg(Fig, master=parent)
self.canvas.show()
self.canvas.get_tk_widget().grid(row=0,column=0,columnspan=2)
frame.grid(row=0,column=0)
def refreshFigure(self,y):
x=np.arange(len(y))
self.line.set_xdata(x)
self.line.set_ydata(y)
self.canvas.draw()
def main1(self):
self.c13input=int(self.c13.get())
self.Egdata=self.Eg.get()
self.Bzdata=self.Bz.get()
values = plotfunc(self.c13input,self.Egdata,self.Bzdata)
#Just to see whether I actually get the right thing in the input.
print self.c13input
self.refreshFigure(values)
root = Tkinter.Tk()
app = App_Window(root)
root.mainloop()

Categories

Resources