Панель инструментов навигации Matplotlib встроена в окно pyqt5 - сброс исходного вида

Я пытаюсь использовать интерактивные возможности рисования matplotlib, встроенные в окно pyqt5 с версией mpl: 2.1.1

Я привел минимальный пример с файлом пользовательского интерфейса здесь:

https://ufile.io/8irs7

и код питона:

import matplotlib
matplotlib.use("Qt5Agg")
import sys

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure

import pandas as pd
import numpy as np

from PyQt5 import  QtWidgets, uic  # works for pyqt5
from PyQt5.Qt import  QVBoxLayout


# import numpy as np
qtCreatorFile = "window.ui" # Enter file here.
# qtCreatorFile = "pssga.ui"


Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)

class PlotSeries(QtWidgets.QMainWindow, Ui_MainWindow):

    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        self.setupUi(self)

                #create figure
        self.fig=Figure()

        self.axes = self.fig.add_subplot(111)
        self.axes.grid()



        self.canvas=FigureCanvas(self.fig)
        #add plot toolbar from matplotlib
        self.toolbar = NavigationToolbar(self.canvas, self)

        #create new layout
        layout = QVBoxLayout()

        layout.addWidget(self.canvas)
        layout.addWidget(self.toolbar)

        #add layout to qwidget
        self.plotlayout=self.widgetplot.setLayout(layout)

        #draw the canvas ! 
        self.canvas.draw()
        #now let's call widget 
        self.plot_basegraph()


    def plot_basegraph(self):
        #create simple time series 
        for i in range(0,5): 
            ts=pd.Series(np.random.randn(2000), index=pd.date_range('1/1/2015',freq='h', periods=2000),name="series "+str(i))
            if i==0:
                self.stackTSPD=ts.to_frame()
            else:
                self.stackTSPD=self.stackTSPD.join(ts.to_frame(),how='inner')
        print(matplotlib.__version__)

        self.axes.clear()
        self.axes.grid()

        for i in self.stackTSPD.columns.values:
            t=self.stackTSPD.index.to_pydatetime()

            y=self.stackTSPD[i].values
            self.axes.plot(t,y, label=i)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = PlotSeries()
    window.show()
    sys.exit(app.exec_())

Масштабирование временных рядов, панорамирование работает, как и ожидалось, но когда я пытаюсь нажать «Домой» - сбросить исходный вид, происходит сбой:

Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/backends/backend_qt5agg.py", line 155, in __draw_idle_agg
    self.draw()
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/backends/backend_qt5agg.py", line 127, in draw
    super(FigureCanvasQTAggBase, self).draw()
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/backends/backend_agg.py", line 430, in draw
    self.figure.draw(self.renderer)
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/figure.py", line 1299, in draw
    renderer, self, artists, self.suppressComposite)
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/image.py", line 138, in _draw_list_compositing_images
    a.draw(renderer)
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/axes/_base.py", line 2437, in draw
    mimage._draw_list_compositing_images(renderer, self, artists)
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/image.py", line 138, in _draw_list_compositing_images
    a.draw(renderer)
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/axis.py", line 1133, in draw
    ticks_to_draw = self._update_ticks(renderer)
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/axis.py", line 974, in _update_ticks
    tick_tups = list(self.iter_ticks())
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/axis.py", line 917, in iter_ticks
    majorLocs = self.major.locator()
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/dates.py", line 1061, in __call__
    self.refresh()
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/dates.py", line 1081, in refresh
    dmin, dmax = self.viewlim_to_dt()
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/dates.py", line 839, in viewlim_to_dt
    return num2date(vmin, self.tz), num2date(vmax, self.tz)
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/dates.py", line 445, in num2date
    return _from_ordinalf(x, tz)
  File "/usr/local/lib/python3.5/dist-packages/matplotlib/dates.py", line 260, in _from_ordinalf
    dt = datetime.datetime.fromordinal(ix).replace(tzinfo=UTC)
ValueError: ordinal must be >= 1

Такое поведение началось, когда я попытался встроить этот график временных рядов в форму pyqt5. Когда график открывался отдельно в новом окне GTK, он работал без проблем. Я прикрепил пример с работающей домашней кнопкой, которая открывается в окне, отличном от pyqt...

'''
Created on 16. jan. 2018

@author: gregor
'''

import matplotlib as mpl
import matplotlib.pyplot as plt
# matplotlib.use("Qt5Agg")


# from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
# from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
# from matplotlib.figure import Figure

import pandas as pd
import numpy as np





class PlotSeries():

    def __init__(self):


        self.fig,self.axes = plt.subplots()

        self.axes.grid()



#         self.canvas=FigureCanvas(self.fig)
        #add plot toolbar from matplotlib
#         self.toolbar = NavigationToolbar(self.canvas, self)

        #create new layout
#         layout = QVBoxLayout()
#         
#         layout.addWidget(self.canvas)
#         layout.addWidget(self.toolbar)
#         
#         #add layout to qwidget
#         self.plotlayout=self.widgetplot.setLayout(layout)
#         
#         #draw the canvas ! 
#         self.canvas.draw()
        #now let's call widget 
        self.plot_basegraph()


    def plot_basegraph(self):
        #create simple time series 
        for i in range(0,5): 
            ts=pd.Series(np.random.randn(2000), index=pd.date_range('1/1/2015',freq='h', periods=2000),name="series "+str(i))
            if i==0:
                self.stackTSPD=ts.to_frame()
            else:
                self.stackTSPD=self.stackTSPD.join(ts.to_frame(),how='inner')
        print(mpl.__version__)

        self.axes.clear()
        self.axes.grid()

        for i in self.stackTSPD.columns.values:
            t=self.stackTSPD.index.to_pydatetime()

            y=self.stackTSPD[i].values
            self.axes.plot_date(t,y, label=i)
        plt.show()



if __name__ == "__main__":
 PlotSeries()  

Благодарю вас !


person Greg    schedule 15.01.2018    source источник
comment
Чтобы помочь здесь, нужно увидеть минимально воспроизводимый пример проблемы. Без такого примера этот вопрос бесполезен.   -  person ImportanceOfBeingErnest    schedule 15.01.2018
comment
Конечно, я попытаюсь воспроизвести это на минимальном примере...   -  person Greg    schedule 15.01.2018
comment
Вы уверены, что это вообще зависит от использования PyQt? Обычный сюжет, созданный с помощью pyplot и plt.show(), показывает другое поведение?   -  person ImportanceOfBeingErnest    schedule 16.01.2018
comment
Я протестировал пример pyplot и plt.show(), и он работает без проблем. Я прикрепил минимальный пример к сообщению.....   -  person Greg    schedule 17.01.2018


Ответы (1)


Краткий ответ: удалите строку self.canvas.draw().

Объяснение:
вы рисуете холст до того, как он содержит какие-либо данные. Но поскольку в этот момент холст уже оснащен панелью инструментов, «домашнее» состояние — это состояние, в котором оси не имеют данных.

Само по себе это не будет проблемой, но она станет проблемой, когда даты будут нанесены на график.
Начальные ограничения по умолчанию для пустых осей равны (0,1). Таким образом, состояние «Дом» будет относиться к этим ограничениям, и при нажатии кнопки будет предпринята попытка восстановить эти ограничения.
Тем временем, однако, оси было приказано хранить даты. Даты варьируются от (1, whatever). Следовательно, 0 не является допустимым числом на осях дат.

Тогда решение довольно простое. Просто удалите строку self.canvas.draw() из кода. В любом случае он не выполняет ни одной требуемой задачи.

person ImportanceOfBeingErnest    schedule 17.01.2018
comment
Спасибо ! Не ожидал такого простого решения. Я думал, что это как-то связано с github.com/matplotlib/matplotlib/issues/6023 этот предполагаемый баг... Я прожгла 2 дня в поисках решения. По крайней мере, это хороший пример встраивания matplotlib в pyqt5 с помощью пользовательского интерфейса. Спасибо большое за помощь!! - person Greg; 17.01.2018
comment
На будущее: если у вас есть рабочий и нерабочий код, ищите отличия. Здесь помимо всего, что связано с qt, различия import matplotlib.pyplot и canvas.draw(). Затем можно проверить оба, чтобы найти проблему. - person ImportanceOfBeingErnest; 17.01.2018