Python Tkinter,显示实时数据

gywdnpxw  于 2022-11-26  发布在  Python
关注(0)|答案(4)|浏览(438)

我想在GUI中显示实时数据,格式为tkinter。我得到的数据包含两个整数[current, voltage]list。我每秒都得到新数据。
我设法创建了一个GUI,现在我想知道如何在GUI Label小部件(python tkinter)中显示数据并动态更新标签。
下面是我目前的代码:

#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()
6l7fqoea

6l7fqoea1#

如果您想要绘制即时数据的图形,而且不想使用其他程式库来替您完成这项工作,您可能会发现下列内容是建立您自己图形的启发性起点。当评估标准程式库中的math.sin函式时,范例会绘制完整的值圆。程式码会考虑自动采样、调整大小和视需要更新,而且应该会有相当的回应。

#! /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()
ff29svar

ff29svar2#

您可以动态变更标示文字:
这是一种将textvariable选项与StringVar.set()一起使用的方法

str_var = tk.StringVar(value="Default")

currentValues= Label(win, textvariable=my_string_var)
currentValues.place(x=200, y=100)

str_var.set("New value")

另一种方法简单地使用.configure()方法

currentValues = Label(win, text = "default")
currentValues.configure(text="New value")

最后,要使UI更新而不等待循环的其余部分,请执行更新

win.update()
wfveoks0

wfveoks03#

在浏览了有关实时图的some demos in Stock overflow的详细信息后,我终于找到了在我为 * Keithley 2410 * 编写的 * 类对象 * 中绘制实时电流-电压的方法,在该对象中,我想绘制从用于扫描电压的for loop中读取的实时电流-电压数据。
整个脚本如下图:

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')
    
        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(): 
            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()

这段代码运行正常,输出结果如Graph所示。我希望它能对您有所帮助。x1c 0d1x。

whitzsjs

whitzsjs4#

我想在GUI中显示一些实时数据。
我想你要做的是使用.after()方法。.after()方法将tkinter排队,在设定的时间后运行一些代码。
例如:

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

after方法中的1000个单位是最接近1秒的精确值。

相关问题