Tkinter、Matplotlib与线程
在主循环中,创建了一个2 x 2的tkinter网格,第一行每个单元格里都有一个标签。第二行则创建了两个Matplotlib图形,并且有一个子图。两个函数负责动态刷新这个网格,它们各自运行在一个线程中。第一行(两个标签)通过这两个函数刷新得很好。但是,第二行什么都没有……没有图表!
from tkinter import *
import threading
import time
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import style
def read_api1():
n = 0
while 1:
n = n + 1
texte1.config(text="fig1 " + str(n))
ax1.plot([1,2], [12,14])
time.sleep(2)
ax1.cla()
def read_api2():
m = 0
while 1:
m = m + 1
texte2.config(text="fig2 " + str(m))
ax2.plot([.1,.2,.3], [2,4,3])
time.sleep(1)
main = Tk()
style.use("ggplot")
texte1 = Label(main, text="fig1")
texte1.grid(row=0,column=0)
fig1 = Figure(figsize=(2, 2), dpi=112)
ax1 = fig1.add_subplot()
fig1.set_tight_layout(True)
graph = FigureCanvasTkAgg(fig1, master=main)
canvas = graph.get_tk_widget()
canvas.grid(row=1, column=0)
texte2 = Label(main, text="fig2")
texte2.grid(row=0,column=1)
fig2 = Figure(figsize=(2, 2), dpi=112)
ax2 = fig2.add_subplot()
fig2.set_tight_layout(True)
graph = FigureCanvasTkAgg(fig2, master=main)
canvas = graph.get_tk_widget()
canvas.grid(row=1, column=1)
t = threading.Thread(target=read_api1)
t.start()
t = threading.Thread(target=read_api2)
t.start()
main.mainloop()
任何帮助都非常感谢 :)
编辑:
更多细节 @furas:
read_api
2是用来在特定时间从WEB API
获取数据的。所以你推荐的after
方法应该是有效的。read_api1
是用来从串口(GPIO UART
)获取数据的。所以这个线程会一直等待数据可以读取。
在这种情况下,我不太明白怎么使用after
方法。
换句话说,问题是:如何在tkinter环境中根据异步输入刷新Matplotlib图表?异步串口数据读取不能放在主循环里,所以我把它放在了线程中,但即使使用graph.draw()
也不行。有什么建议吗?
相关问题:
- 暂无相关问题
1 个回答
0
这里有两个问题:
需要用
graph.draw()
来更新或重新绘制图表。通常图形用户界面(GUI)不喜欢在线程中运行,而
graph.draw()
似乎在线程中无法工作(至少在我的Linux系统上是这样)。
你可能需要使用 main.after(1000, main_api1)
来在 1000毫秒
(即 1秒
)后运行同一个函数,这样就不需要使用 thread
,也不会阻塞 mainloop()
。
import tkinter as tk # PEP8: `import *` is not preferred
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import style
import random
# --- functions ---
def read_api1():
global n
n = n + 1
texte1.config(text="fig1 " + str(n))
ax1.cla()
ax1.plot([1,2], [random.randint(0,10),random.randint(0,10)])
graph1.draw()
main.after(1000, read_api1)
def read_api2():
global m
m = m + 1
texte2.config(text="fig2 " + str(m))
ax2.cla()
ax2.plot([.1,.2,.3], [random.randint(0,10),random.randint(0,10),random.randint(0,10)])
graph2.draw()
main.after(1000, read_api2)
# --- main ---
m = 0
n = 0
main = tk.Tk()
style.use("ggplot")
texte1 = tk.Label(main, text="fig1")
texte1.grid(row=0, column=0)
fig1 = Figure(figsize=(2, 2), dpi=112)
ax1 = fig1.add_subplot()
fig1.set_tight_layout(True)
graph1 = FigureCanvasTkAgg(fig1, master=main)
canvas1 = graph1.get_tk_widget()
canvas1.grid(row=1, column=0)
#graph1.draw()
texte2 = tk.Label(main, text="fig2")
texte2.grid(row=0,column=1)
fig2 = Figure(figsize=(2, 2), dpi=112)
ax2 = fig2.add_subplot()
fig2.set_tight_layout(True)
graph2 = FigureCanvasTkAgg(fig2, master=main)
canvas2 = graph2.get_tk_widget()
canvas2.grid(row=1, column=1)
#graph2.draw()
read_api1()
read_api2()
main.mainloop()
编辑:这是一个示例,展示了如何运行两个线程。每个线程以不同的速度生成数据,并使用两个 queue
将数据发送到主线程。主线程使用两个 after()
来检查这两个队列,并更新两个图表。
import tkinter as tk # PEP8: `import *` is not preferred
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import style
import random
import threading
import queue
import time
# --- functions ---
def WEB_API(queue):
# it will run in thread
print('WEB_API: start')
while web_api_running:
value = random.randint(1, 3)
time.sleep(.1)
print('WEB_API:', value)
queue.put(value)
def GPIO_API(queue):
# it will run in thread
print('GPIO_API: start')
while gpio_api_running:
value = random.randint(1, 3)
time.sleep(value)
print('GPIO_API:', value)
queue.put(value)
def read_api1():
global n
global data1
if not queue1.empty():
value = queue1.get()
# remove first item and add new item at the the end
data1 = data1[1:] + [value]
n += 1
texte1.config(text="fig1 " + str(n))
ax1.cla()
ax1.plot(range(10), data1)
graph1.draw()
main.after(100, read_api1)
def read_api2():
global m
global data2
if not queue2.empty():
value = queue2.get()
# remove first item and add new item at the the end
data2 = data2[1:] + [value]
m = m + 1
texte2.config(text="fig2 " + str(m))
ax2.cla()
ax2.plot([.1,.2,.3], data2)
graph2.draw()
main.after(100, read_api2)
# --- before GUI ---
# default data at start (to add new value at the end and remove first value)
data1 = [0,0,0,0,0,0,0,0,0,0]
data2 = [0,0,0]
m = 0
n = 0
# queues to communicate with threads
queue1 = queue.Queue()
queue2 = queue.Queue()
# global variables to control loops in thread
web_api_running = True
gpio_api_running = True
# start threads and send queues as arguments
thread1 = threading.Thread(target=WEB_API, args=(queue1,))
thread1.start()
thread2 = threading.Thread(target=GPIO_API, args=(queue2,))
thread2.start()
# --- GUI ---
main = tk.Tk()
style.use("ggplot")
texte1 = tk.Label(main, text="fig1")
texte1.grid(row=0, column=0)
fig1 = Figure(figsize=(2, 2), dpi=112)
ax1 = fig1.add_subplot()
fig1.set_tight_layout(True)
graph1 = FigureCanvasTkAgg(fig1, master=main)
canvas1 = graph1.get_tk_widget()
canvas1.grid(row=1, column=0)
texte2 = tk.Label(main, text="fig2")
texte2.grid(row=0,column=1)
fig2 = Figure(figsize=(2, 2), dpi=112)
ax2 = fig2.add_subplot()
fig2.set_tight_layout(True)
graph2 = FigureCanvasTkAgg(fig2, master=main)
canvas2 = graph2.get_tk_widget()
canvas2.grid(row=1, column=1)
# draw plots first time
ax1.plot(range(10), data1)
ax2.plot([.1,.2,.3], data2)
# run after which will update data and redraw plots
read_api1()
read_api2()
main.mainloop()
# --- after GUI ---
# stop loops in threads
web_api_running = False
gpio_api_running = False
# wait for end of theads
thread1.join()
thread2.join()