У меня есть относительно простая ситуация с использованием навигационной панели Matplotlib. Я хочу иметь возможность сохранить предыдущее значение масштабирования, панорамирование камеры и т.д. Между обновлениями фигур. Я сохранил здесь встроенный PyQt5 (который я использую в своем проекте), если между ними есть дополнительная взаимосвязь. Большое вам спасибо за то, что посмотрели!
import sys
import os
import random
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QGridLayout, QFileDialog, QPushButton
from numpy import arange
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
class MyMplCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.fig = fig ###
self.axes = fig.add_subplot(111)
self.axes.hold(False)
self.compute_initial_figure()
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def compute_initial_figure(self):
pass
class MyDynamicMplCanvas(MyMplCanvas):
def __init__(self, *args, **kwargs):
MyMplCanvas.__init__(self, *args, **kwargs)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_figure)
timer.start(1000)
def compute_initial_figure(self):
self.axes.plot([0, 1, 2, 3], [1, 2, 0, 4], 'b')
def update_figure(self):
l = [random.randint(0, 10) for i in range(4)]
self.axes.cla()
self.axes.plot([0, 1, 2, 3], l, 'b')
self.draw()
class P1(QtWidgets.QWidget):
def __init__(self, parent=None):
super(P1, self).__init__(parent)
layout = QGridLayout(self)
self.plot_canvas = MyDynamicMplCanvas(self, width=5, height=4, dpi=100)
self.navi_toolbar = NavigationToolbar(self.plot_canvas, self)
layout.addWidget(self.plot_canvas, 1, 1, 1, 1)
layout.addWidget(self.navi_toolbar, 2, 1, 1, 1)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.stack = QtWidgets.QStackedWidget(self)
P1f = P1(self)
self.stack.addWidget(P1f)
self.setCentralWidget(self.stack)
if __name__ == '__main__':
qApp = QtWidgets.QApplication(sys.argv)
aw = MainWindow()
aw.show()
sys.exit(qApp.exec_())
axes.hold(False)
злодеями здесь являются axes.hold(False)
и axes.cla()
. Они отвечают за очистку осей и фигуры, которые (как правило) сбросят вид сюжета.
С их помощью вы можете правильно использовать self.axes.autoscale(enable=False)
, который я рекомендую вам поставить после первого участка в compute_initial_figure()
, поэтому график, по крайней мере, немного масштабируется в начале.
Затем, чтобы очистить предыдущие сюжеты, вы можете создать еще один атрибут для класса MyMplCanvas
, возможно, self.plotted_line
или что-то вроде этого, инициализированного None
. Каждый раз, когда вы вызываете self.axes.plot(...)
, присваиваете возвращаемое значение self.plotted_line
, например: self.plotted_line, = self.axes.plot(...)
. Обратите внимание на запятую после self.plotted_line
, которая является одним из способов присвоить только первое возвращаемое значение, которое вас интересует.
Наконец, прямо перед каждым новым сюжетом, проверьте и вызовите
if self.plotted_line is not None:
self.plot.remove()
который эффективно удалит предыдущий сюжет.
Классы холста будут выглядеть примерно так (изменения были незначительными).
class MyMplCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.fig = fig ###
self.axes = fig.add_subplot(111)
self.plotted_line = None
self.compute_initial_figure()
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def compute_initial_figure(self):
pass
class MyDynamicMplCanvas(MyMplCanvas):
def __init__(self, *args, **kwargs):
MyMplCanvas.__init__(self, *args, **kwargs)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_figure)
timer.start(1000)
def compute_initial_figure(self):
self.plotted_line, = self.axes.plot([0, 1, 2, 3], [1, 2, 0, 4], 'b')
self.axes.autoscale(enable=False)
def update_figure(self):
l = [random.randint(0, 10) for i in range(4)]
if self.plotted_line is not None:
self.plotted_line.remove()
self.plotted_line, = self.axes.plot([0, 1, 2, 3], l, 'b')
self.draw()
Значения масштабирования можно сохранить напрямую, соединив mpl 'draw_event' с:
def __init__(self, parent=None, width=5, height=4, dpi=100):
...
self.mpl_connect('draw_event', self.on_draw)
def on_draw(self, event):
self.xlim = self.axes.get_xlim()
self.ylim = self.axes.get_ylim()
и затем восстанавливается после вызова plot():
def update_figure(self):
...
self.axes.plot([0, 1, 2, 3], l, 'b')
self.axes.set_xlim(self.xlim)
self.axes.set_ylim(self.ylim)
...
https://matplotlib.org/users/event_handling.html
Полный код:
import random
import sys
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QGridLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
class MyMplCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.fig = fig ###
self.axes = fig.add_subplot(111)
self.axes.hold(False)
self.compute_initial_figure()
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.xlim = self.axes.get_xlim()
self.ylim = self.axes.get_ylim()
self.mpl_connect('draw_event', self.on_draw)
def on_draw(self, event):
self.xlim = self.axes.get_xlim()
self.ylim = self.axes.get_ylim()
def compute_initial_figure(self):
pass
class MyDynamicMplCanvas(MyMplCanvas):
def __init__(self, *args, **kwargs):
MyMplCanvas.__init__(self, *args, **kwargs)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_figure)
timer.start(1000)
def compute_initial_figure(self):
self.axes.plot([0, 1, 2, 3], [1, 2, 0, 4], 'b')
def update_figure(self):
l = [random.randint(0, 10) for i in range(4)]
self.axes.cla()
self.axes.plot([0, 1, 2, 3], l, 'b')
self.axes.set_xlim(self.xlim)
self.axes.set_ylim(self.ylim)
self.draw()
class P1(QtWidgets.QWidget):
def __init__(self, parent=None):
super(P1, self).__init__(parent)
layout = QGridLayout(self)
self.plot_canvas = MyDynamicMplCanvas(self, width=5, height=4, dpi=100)
self.navi_toolbar = NavigationToolbar(self.plot_canvas, self)
layout.addWidget(self.plot_canvas, 1, 1, 1, 1)
layout.addWidget(self.navi_toolbar, 2, 1, 1, 1)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.stack = QtWidgets.QStackedWidget(self)
P1f = P1(self)
self.stack.addWidget(P1f)
self.setCentralWidget(self.stack)
if __name__ == '__main__':
qApp = QtWidgets.QApplication(sys.argv)
aw = MainWindow()
aw.show()
sys.exit(qApp.exec_())