用Python创建动态更新图

col17t5w  于 2023-06-28  发布在  Python
关注(0)|答案(6)|浏览(191)

我需要写一个脚本在Python中,将采取动态变化的数据,数据的来源是不是问题在这里,并显示在屏幕上的图形。
我知道如何使用matplotlib,但matplotlib的问题是我只能在脚本的末尾显示一次图形。我需要能够不仅显示图形一次,而且还更新它的飞行,每次数据更改时。
我发现可以使用wxPython和matplotlib来实现这一点,但对我来说有点复杂,因为我对wxPython一点也不熟悉。
因此,如果有人能向我展示如何使用wxPython和matplotlib来显示和更新简单图形的简单示例,我将非常高兴。或者,如果有其他方法可以做到这一点,这对我也有好处。

更新

PS:因为没有人回答,所以@janislaw注意到了matplotlib的帮助,并写了一些代码。这是一个虚拟的例子:

import time
import matplotlib.pyplot as plt

def data_gen():
    a=data_gen.a
    if a>10:
        data_gen.a=1
    data_gen.a=data_gen.a+1
    return range (a,a+10)
    
def run(*args):
    background = fig.canvas.copy_from_bbox(ax.bbox)

    while 1:
        time.sleep(0.1)
        # restore the clean slate background
        fig.canvas.restore_region(background)
        # update the data
        ydata = data_gen()
        xdata=range(len(ydata))

        line.set_data(xdata, ydata)

        # just draw the animated artist
        ax.draw_artist(line)
        # just redraw the axes rectangle
        fig.canvas.blit(ax.bbox)

data_gen.a=1
fig = plt.figure()
ax = fig.add_subplot(111)
line, = ax.plot([], [], animated=True)
ax.set_ylim(0, 20)
ax.set_xlim(0, 10)
ax.grid()

manager = plt.get_current_fig_manager()
manager.window.after(100, run)

plt.show()

这个实现有一些问题,比如如果你试图移动窗口,脚本会停止。但基本上可以使用。

omjgkv6w

omjgkv6w1#

下面是我写的一个类来处理这个问题。它接受您传递给它的matplotlib图,并将其放置在GUI窗口中。它在自己的线程中,这样即使程序很忙碌,它也能保持响应。

import Tkinter
import threading
import matplotlib
import matplotlib.backends.backend_tkagg

class Plotter():
    def __init__(self,fig):
        self.root = Tkinter.Tk()
        self.root.state("zoomed")

        self.fig = fig
        t = threading.Thread(target=self.PlottingThread,args=(fig,))
        t.start()

    def PlottingThread(self,fig):     
        canvas = matplotlib.backends.backend_tkagg.FigureCanvasTkAgg(fig, master=self.root)
        canvas.show()
        canvas.get_tk_widget().pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)

        toolbar = matplotlib.backends.backend_tkagg.NavigationToolbar2TkAgg(canvas, self.root)
        toolbar.update()
        canvas._tkcanvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)

        self.root.mainloop()

在代码中,需要如下初始化绘图仪:

import pylab
fig = matplotlib.pyplot.figure()
Plotter(fig)

然后你可以像这样绘制它:

fig.gca().clear()
fig.gca().plot([1,2,3],[4,5,6])
fig.canvas.draw()
qncylg1j

qncylg1j2#

作为matplotlib的替代品,查科库提供了很好的绘图功能,并且在某些方面更适合实时绘图。
请看一些屏幕截图here,特别是这些示例:

查科有qt和wx的后端,所以它在大多数时候都能很好地处理底层细节。

g52tjvyc

g52tjvyc3#

你可以使用matplotlib.pyplot.show(block=False)来代替matplotlib.pyplot.show()。此调用不会阻止程序进一步执行。

lfapxunr

lfapxunr4#

动态绘图的例子,秘诀是在绘图时做一个暂停,这里我使用networkx:

G.add_node(i,)
    G.add_edge(vertic[0],vertic[1],weight=0.2)
    print "ok"
    #pos=nx.random_layout(G)
    #pos = nx.spring_layout(G)
    #pos = nx.circular_layout(G)
    pos = nx.fruchterman_reingold_layout(G)

    nx.draw_networkx_nodes(G,pos,node_size=40)
    nx.draw_networkx_edges(G,pos,width=1.0)
    plt.axis('off') # supprimer les axes

    plt.pause(0.0001)
    plt.show()  # display
bpsygsoo

bpsygsoo5#

我需要创建一个随时间更新的图表。我想到的最方便的解决方案是每次创建一个新的图形。问题是脚本在创建第一个图形后不会执行,除非手动关闭窗口。通过打开交互模式避免了该问题,如下所示

for i in range(0,100): 
      fig1 = plt.figure(num=1,clear=True) # a figure is created with the id of 1
      createFigure(fig=fig1,id=1) # calls a function built by me which would insert data such that figure is 3d scatterplot
      plt.ion() # this turns the interactive mode on
      plt.show() # create the graph
      plt.pause(2) # pause the script for 2 seconds , the number of seconds here determine the time after that graph refreshes

这里有两点要注意
1.图的id-如果图的id被改变,则每次将创建新的图,但是如果图的id相同,则相关的图将被更新。
1.pause函数-在指定的时间段内停止执行代码。如果不应用此选项,图形将几乎立即刷新

lsmd5eda

lsmd5eda6#

我已经创建了一个类,它用matplotlib绘图来绘制tkinter小部件。绘图动态更新(或多或少实时更新)。

    • 在python 3.10、matplotlib 3.6.0和tkinter 8.6中测试。*
from matplotlib import pyplot as plt
from matplotlib import animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk

from tkinter import *

class MatplotlibPlot:
    def __init__(
            self, master, datas: list[dict], update_interval_ms: int = 200, padding: int = 5,
            fig_config: callable = None, axes_config: callable = None
    ):
        """
        Creates a Matplotlib plot in a Tkinter environment. The plot is dynamic, i.e., the plot data is periodically
        drawn and the canvas updates.
        @param master: The master widget where the pot will be rendered.
        @param datas: A list containing dictionaries of data. Each dictionary must have a `x` key, which holds the xx
        data, and `y` key, which holds the yy data. The other keys are optional and are used as kwargs of
        `Axes.plot()` function. Each list entry, i.e., each dict, is drawn as a separate line.
        @param fig_config: A function that is called after the figure creation. This function can be used to configure
        the figure. The function signature is `fig_config(fig: pyplot.Figure) -> None`. The example bellow allows
        the configuration of the figure title and Dots Per Inch (DPI).
        ``` python
        my_vars = [{"x": [], "y": [], "label": "Label"}, ]

        window = Tk()

        def my_fig_config(fig: pyplot.Figure) -> None:
            fig.suptitle("Superior Title")
            fig.set_dpi(200)

        MatplotlibPlot(master=window, datas=my_vars, fig_config=my_fig_config)

        window.mainloop()
        ```
        @param axes_config: A function that is called after the axes creation. This function can be used to configure
        the axes. The function signature is `axes_config(axes: pyplot.Axes) -> None`. The example bellow allows
        the configuration of the axes xx and yy label, the axes title and also enables the axes legend.
        ``` python
        my_vars = [{"x": [], "y": [], "label": "Label"}, ]

        window = Tk()

        def my_axes_config(axes: pyplot.Axes) -> None:
            axes.set_xlabel("XX Axis")
            axes.set_ylabel("YY Axis")
            axes.set_title("Axes Title")
            axes.legend()

        MatplotlibPlot(master=window, datas=my_vars, axes_config=my_axes_config)

        window.mainloop()
        ```
        @param update_interval_ms: The plot update interval in milliseconds (ms). Defaults to 200 ms.
        @param padding: The padding, in pixels (px), to be used between widgets. Defaults to 5 px.
        """

        # Creates the figure
        fig = plt.Figure()
        # Calls the config function if passed
        if fig_config:
            fig_config(fig)

        # Creates Tk a canvas
        canvas = FigureCanvasTkAgg(figure=fig, master=master)
        # Allocates the canvas
        canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=True, padx=padding, pady=padding)
        # Creates the toolbar
        NavigationToolbar2Tk(canvas=canvas, window=master, pack_toolbar=True)

        # Creates an axes
        axes = fig.add_subplot(1, 1, 1)

        # For each data entry populate the axes with the initial data values. Also, configures the lines with the
        # extra key-word arguments.
        for data in datas:
            axes.plot(data["x"], data["y"])
            _kwargs = data.copy()
            _kwargs.pop("x")
            _kwargs.pop("y")
            axes.lines[-1].set(**_kwargs)

        # Calls the config function if passed
        if axes_config:
            axes_config(axes)

        # Creates a function animation which calls self.update_plot function.
        self.animation = animation.FuncAnimation(
            fig=fig,
            func=self.update_plot,
            fargs=(canvas, axes, datas),
            interval=update_interval_ms,
            repeat=False,
            blit=True
        )

    # noinspection PyMethodMayBeStatic
    def update_plot(self, _, canvas, axes, datas):
        # Variables used to update xx and yy axes limits.
        update_canvas = False
        xx_max, xx_min = axes.get_xlim()
        yy_max, yy_min = axes.get_ylim()

        # For each data entry update its correspondent axes line
        for line, data in zip(axes.lines, datas):
            line.set_data(data["x"], data["y"])
            _kwargs = data.copy()
            _kwargs.pop("x")
            _kwargs.pop("y")
            line.set(**_kwargs)

            # If there are more than two points in the data then update xx and yy limits.
            if len(data["x"]) > 1:
                if min(data["x"]) < xx_min:
                    xx_min = min(data["x"])
                    update_canvas = True
                if max(data["x"]) > xx_max:
                    xx_max = max(data["x"])
                    update_canvas = True
                if min(data["y"]) < yy_min:
                    yy_min = min(data["y"])
                    update_canvas = True
                if max(data["y"]) > yy_max:
                    yy_max = max(data["y"])
                    update_canvas = True

        # If limits need to be updates redraw canvas
        if update_canvas:
            axes.set_xlim(xx_min, xx_max)
            axes.set_ylim(yy_min, yy_max)
            canvas.draw()

        # return the lines
        return axes.lines

以下是用于更新tkinter图中绘制的数据的自定义tkinter比例的示例。

from matplotlib import pyplot as plt
from matplotlib import animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk

from tkinter import *

class MatplotlibPlot:
    def __init__(
            self, master, datas: list[dict], update_interval_ms: int = 200, padding: int = 5,
            fig_config: callable = None, axes_config: callable = None
    ):
        """
        Creates a Matplotlib plot in a Tkinter environment. The plot is dynamic, i.e., the plot data is periodically
        drawn and the canvas updates.
        @param master: The master widget where the pot will be rendered.
        @param datas: A list containing dictionaries of data. Each dictionary must have a `x` key, which holds the xx
        data, and `y` key, which holds the yy data. The other keys are optional and are used as kwargs of
        `Axes.plot()` function. Each list entry, i.e., each dict, is drawn as a separate line.
        @param fig_config: A function that is called after the figure creation. This function can be used to configure
        the figure. The function signature is `fig_config(fig: pyplot.Figure) -> None`. The example bellow allows
        the configuration of the figure title and Dots Per Inch (DPI).
        ``` python
        my_vars = [{"x": [], "y": [], "label": "Label"}, ]

        window = Tk()

        def my_fig_config(fig: pyplot.Figure) -> None:
            fig.suptitle("Superior Title")
            fig.set_dpi(200)

        MatplotlibPlot(master=window, datas=my_vars, fig_config=my_fig_config)

        window.mainloop()
        ```
        @param axes_config: A function that is called after the axes creation. This function can be used to configure
        the axes. The function signature is `axes_config(axes: pyplot.Axes) -> None`. The example bellow allows
        the configuration of the axes xx and yy label, the axes title and also enables the axes legend.
        ``` python
        my_vars = [{"x": [], "y": [], "label": "Label"}, ]

        window = Tk()

        def my_axes_config(axes: pyplot.Axes) -> None:
            axes.set_xlabel("XX Axis")
            axes.set_ylabel("YY Axis")
            axes.set_title("Axes Title")
            axes.legend()

        MatplotlibPlot(master=window, datas=my_vars, axes_config=my_axes_config)

        window.mainloop()
        ```
        @param update_interval_ms: The plot update interval in milliseconds (ms). Defaults to 200 ms.
        @param padding: The padding, in pixels (px), to be used between widgets. Defaults to 5 px.
        """

        # Creates the figure
        fig = plt.Figure()
        # Calls the config function if passed
        if fig_config:
            fig_config(fig)

        # Creates Tk a canvas
        canvas = FigureCanvasTkAgg(figure=fig, master=master)
        # Allocates the canvas
        canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=True, padx=padding, pady=padding)
        # Creates the toolbar
        NavigationToolbar2Tk(canvas=canvas, window=master, pack_toolbar=True)

        # Creates an axes
        axes = fig.add_subplot(1, 1, 1)

        # For each data entry populate the axes with the initial data values. Also, configures the lines with the
        # extra key-word arguments.
        for data in datas:
            axes.plot(data["x"], data["y"])
            _kwargs = data.copy()
            _kwargs.pop("x")
            _kwargs.pop("y")
            axes.lines[-1].set(**_kwargs)

        # Calls the config function if passed
        if axes_config:
            axes_config(axes)

        # Creates a function animation which calls self.update_plot function.
        self.animation = animation.FuncAnimation(
            fig=fig,
            func=self.update_plot,
            fargs=(canvas, axes, datas),
            interval=update_interval_ms,
            repeat=False,
            blit=True
        )

    # noinspection PyMethodMayBeStatic
    def update_plot(self, _, canvas, axes, datas):
        # Variables used to update xx and yy axes limits.
        update_canvas = False
        xx_max, xx_min = axes.get_xlim()
        yy_max, yy_min = axes.get_ylim()

        # For each data entry update its correspondent axes line
        for line, data in zip(axes.lines, datas):
            line.set_data(data["x"], data["y"])
            _kwargs = data.copy()
            _kwargs.pop("x")
            _kwargs.pop("y")
            line.set(**_kwargs)

            # If there are more than two points in the data then update xx and yy limits.
            if len(data["x"]) > 1:
                if min(data["x"]) < xx_min:
                    xx_min = min(data["x"])
                    update_canvas = True
                if max(data["x"]) > xx_max:
                    xx_max = max(data["x"])
                    update_canvas = True
                if min(data["y"]) < yy_min:
                    yy_min = min(data["y"])
                    update_canvas = True
                if max(data["y"]) > yy_max:
                    yy_max = max(data["y"])
                    update_canvas = True

        # If limits need to be updates redraw canvas
        if update_canvas:
            axes.set_xlim(xx_min, xx_max)
            axes.set_ylim(yy_min, yy_max)
            canvas.draw()

        # return the lines
        return axes.lines

class CustomScaler:
    def __init__(self, master, init: int = None, start: int = 0, stop: int = 100,
                 padding: int = 5, callback: callable = None):
        """
        Creates a scaler with an increment and decrement button and a text entry.
        @param master: The master Tkinter widget.
        @param init: The scaler initial value.
        @param start: The scaler minimum value.
        @param stop: The scaler maximum value.
        @param padding: The widget padding.
        @param callback: A callback function that is called each time that the scaler changes its value. The function
        signature is `callback(var_name: str, var_index: int, var_mode: str) -> None`.
        """
        self.start = start
        self.stop = stop

        if init:
            self.value = IntVar(master=master, value=init, name="scaler_value")
        else:
            self.value = IntVar(master=master, value=(self.stop - self.start) // 2, name="scaler_value")

        if callback:
            self.value.trace_add("write", callback=callback)

        Scale(master=master, from_=self.start, to=self.stop, orient=HORIZONTAL, variable=self.value) \
            .pack(side=TOP, expand=True, fill=BOTH, padx=padding, pady=padding)
        Button(master=master, text="◀", command=self.decrement, repeatdelay=500, repeatinterval=5) \
            .pack(side=LEFT, fill=Y, padx=padding, pady=padding)
        Button(master=master, text="▶", command=self.increment, repeatdelay=500, repeatinterval=5) \
            .pack(side=RIGHT, fill=Y, padx=padding, pady=padding)
        Entry(master=master, justify=CENTER, textvariable=self.value) \
            .pack(fill=X, expand=False, padx=padding, pady=padding)

    def decrement(self):
        _value = self.value.get()
        if _value <= self.start:
            return
        self.value.set(_value - 1)

    def increment(self):
        _value = self.value.get()
        if _value >= self.stop:
            return
        self.value.set(_value + 1)

def scaler_changed(my_vars: list[dict], scaler: CustomScaler) -> None:
    my_vars[0]["x"].append(len(my_vars[0]["x"]))
    my_vars[0]["y"].append(scaler.value.get())

def my_axes_config(axes: plt.Axes) -> None:
    axes.set_xlabel("Sample")
    axes.set_ylabel("Value")
    axes.set_title("Scaler Values")

def main():
    my_vars = [{"x": [], "y": []}, ]

    window = Tk()
    window.rowconfigure(0, weight=10)
    window.rowconfigure(1, weight=90)

    frame_scaler = Frame(master=window)
    frame_scaler.grid(row=0, column=0)
    scaler = CustomScaler(
        master=frame_scaler, start=0, stop=100, callback=lambda n, i, m: scaler_changed(my_vars, scaler)
    )

    frame_plot = Frame(master=window)
    frame_plot.grid(row=1, column=0)
    MatplotlibPlot(master=frame_plot, datas=my_vars, axes_config=my_axes_config, update_interval_ms=10)

    window.mainloop()

if __name__ == "__main__":
    main()

上面的示例生成以下窗口。

相关问题