使用 Python 创建动态更新图

发布于 2024-10-31 06:40:30 字数 1346 浏览 0 评论 0原文

我需要用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()

此实现存在问题,例如如果您尝试移动窗口,脚本就会停止。不过基本上是可以用的。

I need to write a script in Python that will take dynamically changed data, the source of data is not matter here, and display graph on the screen.

I know how to use matplotlib, but the problem with matplotlib is that I can display graph only once, at the end of the script. I need to be able not only to display graph one time, but also update it on the fly, each time when data changes.

I found that it is possible to use wxPython with matplotlib to do this, but it is little bit complicate to do this for me, because I am not familiar with wxPython at all.

So I will be very happy if someone will show me simple example how to use wxPython with matplotlib to show and update simple graph. Or, if it is some other way to do this, it will be good to me too.

Update

PS: since no one answered and looked at matplotlib help noticed by @janislaw and wrote some code. This is some dummy example:


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

This implementation have problems, like script stops if you trying to move the window. But basically it can be used.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

夜还是长夜 2024-11-07 06:40:30

这是我写的一个处理这个问题的类。它需要您传递给它的 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()

Here is a class I wrote that handles this issue. It takes a matplotlib figure that you pass to it and places it in a GUI window. Its in its own thread so that it stays responsive even when your program is busy.

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

In your code, you need to initialize the plotter like this:

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

Then you can plot to it like this:

fig.gca().clear()
fig.gca().plot([1,2,3],[4,5,6])
fig.canvas.draw()
十二 2024-11-07 06:40:30

作为 matplotlib 的替代品,Chaco 库提供了很好的绘图功能,并且在某些方面更适合实时绘图。

请参阅此处的一些屏幕截图,特别是请参阅以下示例:

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

As an alternative to matplotlib, the Chaco library provides nice graphing capabilities and is in some ways better-suited for live plotting.

See some screenshots here, and in particular, see these examples:

Chaco has backends for qt and wx, so it handles the underlying details for you rather nicely most of the time.

错爱 2024-11-07 06:40:30

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

Instead of matplotlib.pyplot.show() you can just use matplotlib.pyplot.show(block=False). This call will not block the program to execute further.

﹉夏雨初晴づ 2024-11-07 06:40:30

动态绘图的示例,秘密是在绘图时暂停,这里我使用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

example of dynamic plot , the secret is to do a pause while plotting , here i use 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
绳情 2024-11-07 06:40:30

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

    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 更改为新的每次都会创建图表,但如果相同,则会更新相关图表。
  2. 暂停函数 - 这会阻止代码在指定的时间段内执行。如果不应用这一点,图表将几乎立即刷新

I had the need to create a graph that updates with time. The most convenient solution I came up was to create a new graph each time. The issue was that the script won't be executed after the first graph is created, unless the window is closed manually.
That issue was avoided by turning the interactive mode on as shown below

    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

There are two important points to note here

  1. id of the figure - if the id of the figure is changed a new graph will be created every time, but if it is same it relevant graph would be updated.
  2. pause function - this stops the code from executing for the specified time period. If this is not applied graph will refresh almost immediately
酷炫老祖宗 2024-11-07 06:40:30

我创建了一个类,它使用 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()

上面的示例生成以下窗口。
输入图片此处描述

I have created a class that draws a tkinter widget with a matplotlib plot. The plot is updated dynamically (more or less in realtime).

  • Tested in python 3.10, matplotlib 3.6.0 and 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

Below is an example of a custom tkinter scale used to update data which is drawn in the tkinter plot.

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

The example above produces the following window.
enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文