在tkinter gui中使用导航栏后,matplotlib实时绘图relim

时间:2018-10-12 14:09:12

标签: python python-3.x matplotlib tkinter

我正在用实时嵌入式matplotlib图在tkinter中制作一个gui。我将FigureCanvasTkAgg用于画布,将NavigationToolbar2Tk用于导航栏,并将FuncAnimation用于处理给定数据源的定期更新。

FuncAnimation关联的回调在每次调用(即Axes.plot(...))时都会在给定行(即Line2D.set_data(...)的返回值)上重置数据。回调还会重新确定并应用适当的x轴和y轴限制,以通过以下方式容纳新数据

axis.relim()
axis.autoscale_view()

其中axisAxesSubplot的实例。

之前使用导航栏,效果很好;添加的所有新数据都将适当地反映在图形中,并且轴会自动重新缩放以适合它,这就是我的目标。

我面临的问题是,如果使用导航栏上的任何功能(平移,缩放等),重新缩放将不再起作用,这意味着图形可能会超出视线,并且用户的查看新数据的唯一方法是手动将其平移或手动缩小,这是不希望的。

实际上,此功能很有意义,因为例如仅尝试放大图的一部分以使其立即缩小以将轴重新调整为新数据会很烦人,这就是为什么添加tkinter.Checkbutton暂时禁用重新缩放。

我尝试查看导航栏的源代码,并且似乎更改了轴和画布上的状态,我只能认为是问题所在,但到目前为止,我一直没有找到找到“撤消”这些更改。如果存在这种方式,我可以将其绑定到tkinter.Button或其他东西,以便可以重新启用自动重新缩放。

我该如何解决此问题?

下面是一个演示此问题的最小示例。

import math
import itertools
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.animation import FuncAnimation


def xydata_generator(func, div):
    for num in itertools.count():
        num = num / div
        yield num, func(num)


class Plot(tk.Frame):

    def __init__(self, master, data_source, interval=100, *args, **kwargs):
        super().__init__(master, *args, **kwargs)

        self.data_source = data_source
        self.figure = Figure((5, 5), 100)
        self.canvas = FigureCanvasTkAgg(self.figure, self)
        self.nav_bar = NavigationToolbar2Tk(self.canvas, self)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
        self.axis = self.figure.add_subplot(111)
        self.x_data = []
        self.y_data = []
        self.line = self.axis.plot([], [])[0]  # Axes.plot returns a list
        # Set the data to a mutable type so we only need to append to it then force the line to invalidate its cache
        self.line.set_data(self.x_data, self.y_data)
        self.ani = FuncAnimation(self.figure, self.update_plot, interval=interval)

    def update_plot(self, _):
        x, y = next(self.data_source)  # (realistically the data source wouldn't be restricted to be a generator)
        # Because the Line2D object stores a reference to the two lists, we need only update the lists and signal
        # that the line needs to be updated.
        self.x_data.append(x)
        self.y_data.append(y)
        self.line.recache_always()
        self._refit_artists()

    def _refit_artists(self):
        self.axis.relim()
        self.axis.autoscale_view()


root = tk.Tk()
data = xydata_generator(math.sin, 5)
plot = Plot(root, data)
plot.pack(fill=tk.BOTH, expand=True)
root.mainloop()

1 个答案:

答案 0 :(得分:0)

结果非常简单。要重置轴以使对full_nameAxes.relim()的调用生效,只需调用Axes.autoscale_view()。每次使用导航栏上的功能(平移,缩放等)时,都必须重复此操作。