Я делаю графический интерфейс в tkinter с живыми, встроенными графиками matplotlib.Я использую FigureCanvasTkAgg
для холста, NavigationToolbar2Tk
для панели навигации и FuncAnimation
для обработки периодических обновлений данного источника данных.
Обратный вызов, связанный с FuncAnimation
, сбрасывает данные в данной строке (т. Е. Возвращаемое значение из Axes.plot(...)
) при каждом вызове (т. Е. Line2D.set_data(...)
).Обратный вызов также переопределяет и применяет соответствующие пределы по осям X и Y, чтобы соответствовать новым данным через
axis.relim()
axis.autoscale_view()
, где axis
является экземпляром AxesSubplot
.
Перед используется панель навигации, это прекрасно работает;любые новые добавленные данные соответствующим образом отражаются на графике, и оси автоматически масштабируются, чтобы соответствовать ему, что и было моей целью.
Проблема, с которой я сталкиваюсь, заключается в том, что если используются какие-либо функции на панели навигации(панорамирование, масштабирование и т. д.) повторное масштабирование больше не работает, это означает, что график может вырваться из поля зрения, и единственный способ увидеть новые данные для пользователя - это выполнить панорамирование вручную или уменьшить масштаб вручную, что являетсянежелательно.
Реально, эта функциональность имеет смысл, так как это может раздражать, например, пытаться увеличивать часть графика только для того, чтобы сразу же уменьшить масштаб для повторной установки осей на новые данные, чтопочему я намеревался добавить 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()