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

У меня, надеюсь, относительно простая ситуация с использованием панели инструментов навигации 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_())
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
1 279
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Главными злодеями здесь являются 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()

Большое спасибо, @Gabriel! Прошу прощения за задержку ответа ... Мне очень плохо, но, хотя это исправляет мой пример, я не могу заставить это работать с моей реальной программой (которая использует imshow с дополнительными точками разброса, нанесенными наверх). Я могу создать новую тему только для более детального подхода, но большое спасибо за ваше время и помощь; ваша техника была очень яркой в ​​большем масштабе!

ees 18.06.2018 17:15

Возможно, вы действительно сможете использовать это, возможно, пометив соответствующие графики соответствующим образом, так что пока не теряйте надежды на этот метод. Обязательно подумайте о создании еще одного поста.

Gabriel Jablonski 18.06.2018 17:34

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

Другие вопросы по теме