Как остановить масштабирование навигационной панели инструментов Matplotlib при обновлении графика?

1

У меня есть относительно простая ситуация с использованием навигационной панели 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_())
Теги:
matplotlib
python-3.x
pyqt5
navigationbar

2 ответа

1
Лучший ответ

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()
  • 0
    Большое вам спасибо @Gabriel! Я извиняюсь за задержку с ответом ... Я чувствую себя действительно плохо, но хотя это исправляет мой пример, я не могу заставить его работать с моей настоящей программой (которая использует imshow с дополнительными точками рассеяния, нанесенными сверху). Я могу создать новую тему только для более детального подхода, но большое спасибо за ваше время и помощь; Ваша техника была очень яркой на грандиозном уровне!
  • 0
    Вы действительно можете использовать это, возможно, соответствующим образом пометив соответствующие графики, так что пока не теряйте надежду на этот метод. Подумайте над тем, чтобы создать еще один пост.
0

Значения масштабирования можно сохранить напрямую, соединив 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_())

Ещё вопросы

Сообщество Overcoder
Наверх
Меню