Проблемы с отображением графиков matplotlib в окне PyQt5

В окне нужно отображать разные типы графиков, в зависимости от выбранной даты (или диапазона дат) и radiobutton'ов. Если сначала отображать графики "ЧС по часам" и "ЧС в диапазоне дат", то сами графики занимает весь холст. Если же нарисовать график "Матрица ошибок", то построение вышеуказанных графиков изменится и график будет занимать область в зависимости от данных, которые взяты из БД, а не весь холст. Пример:

Если не рисовать матрицу ошибок введите сюда описание изображения

введите сюда описание изображения

Сама матрица ошибок введите сюда описание изображения

Вид графиков после матрицы ошибок

введите сюда описание изображения

введите сюда описание изображения

Метод, который рисует графики и добавляет их в layout:

def graphic(self):
    self.sc.axes.clear()
    con = sqlite3.connect(DB_FILE_NAME)
    result = {}
    # Матрица ошибок
    if self.radio_pos_neg.isChecked():
        data = np.array(con.cursor().execute('''SELECT predicted_value, actual_value FROM PredictedMessages
                                        WHERE actual_value IS NOT NULL''').fetchall())
        y_test = data[:, 0]
        yhat = data[:, 1]
        cnf_matrix = confusion_matrix(y_test, yhat, labels=[1, 0])
        np.set_printoptions(precision=2)
        self.sc.plot_confusion_matrix(cnf_matrix, classes=['Чрезвычайная ситуация', 'Обычные сообщения'], normalize=False,
                              title='Матрица ошибок')
    # ЧС в конкретный день по часам
    elif self.dt_begin.dateTime() == self.dt_end.dateTime():
        date = self.calendarWidget.selectedDate()
        date = f'{date.year()}-{str(date.month()).rjust(2, "0")}-{str(date.day()).rjust(2, "0")}'
        data = con.cursor().execute(f'''SELECT date, predicted_value FROM PredictedMessages
                                    WHERE date LIKE "{date}%"''').fetchall()
        result = {str(key).rjust(2, '0'): 0 for key in range(24)}
        for item in data:
            hour = item[0].split()[1].split(':')[0]
            result[hour] += 1
        self.sc.axes.plot(list(result.keys()), list(result.values()))
        self.sc.axes.set_title('Количество ЧС по часам', fontsize=12)
        self.sc.axes.set_xlabel('Часы', fontsize=10)
        self.sc.axes.set_ylabel('Количество ЧС', fontsize=10)
    # ЧС в диапазоне дат
    else:
        begin = self.dt_begin.dateTime().toString('yyyy-MM-dd hh:mm:ss')
        end = self.dt_end.dateTime().toString('yyyy-MM-dd hh:mm:ss')
        data = con.cursor().execute('''SELECT date, predicted_value FROM PredictedMessages
                                    WHERE date BETWEEN ? AND ?''',
                                    (begin, end)).fetchall()
        for item in data:
            date = item[0][:10]
            result[date] = result.get(date, 0) + item[1]
        self.sc.axes.bar(list(result.keys()), list(result.values()))
        self.sc.axes.set_title('Количество ЧС по дням', fontsize=12)
        self.sc.axes.set_xlabel('День', fontsize=10)
        self.sc.axes.set_ylabel('Количество ЧС', fontsize=10)
    self.sc.figure.tight_layout()
    self.layout_for_graph.addWidget(self.sc)
    self.sc.draw()

Класс для встраивания графиков и настройки для матрицы ошибок:

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import matplotlib.pyplot as plt
import numpy as np
import itertools


class MplCanvas(FigureCanvasQTAgg):
    def __init__(self, fig):
        self.fig = fig
        self.axes = self.fig.add_subplot(111)
        super(MplCanvas, self).__init__(self.fig)

    def plot_confusion_matrix(self, cm, classes,
                              normalize=False,
                              title='Confusion matrix',
                              cmap=plt.get_cmap('Blues')):
        self.axes.imshow(cm, interpolation='nearest', cmap=cmap)
        self.axes.set_title(title, fontsize=12)
        tick_marks = np.arange(len(classes))
        self.axes.set_xticks(tick_marks)
        self.axes.set_yticks(tick_marks)
        self.axes.set_xticklabels(classes, fontsize=10)
        self.axes.set_yticklabels(classes, fontsize=10)
        thresh = cm.max() / 2.
        for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
            self.axes.text(j, i, format(cm[i, j]),
                           horizontalalignment="center",
                           color="white" if cm[i, j] > thresh else "black")

UPDATE Минимальный воспроизводимый пример.

Класс для рисования графиков

from PyQt5.QtWidgets import QMainWindow, QApplication
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from sklearn.metrics import confusion_matrix
import numpy as np
from Charts import MplCanvas
import sys
from ui import Ui_MainWindow


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.fig = plt.figure(figsize=(5, 4), dpi=100, tight_layout=True)
        self.sc = MplCanvas(self.fig)
        self.toolbar = NavigationToolbar(self.sc, self)
        self.layout_for_graph.addWidget(self.toolbar)
        self.layout_for_graph.addWidget(self.sc)
        self.btn_plot.clicked.connect(self.graphic)
        self.btn_matrix.clicked.connect(self.graphic)

    def graphic(self):
        self.sc.axes.clear()
        # Матрица ошибок
        if self.sender().text() == 'Матрица ошибок':
            y_test = [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                      0, 1, 1, 1]
            yhat = [0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                      0, 1, 1, 1]
            cnf_matrix = confusion_matrix(y_test, yhat, labels=[1, 0])
            np.set_printoptions(precision=2)
            self.sc.plot_confusion_matrix(cnf_matrix, classes=['Чрезвычайная ситуация', 'Обычные сообщения'],
                                          normalize=False,
                                          title='Матрица ошибок')
        # ЧС по дням
        else:
            dates = ['18-05-2021', '19-05-2021', '20-05-2021', '21-05-2021']
            values = [5, 2, 1, 7]
            self.sc.axes.bar(dates, values)
            self.sc.axes.set_title('Количество ЧС по дням', fontsize=12)
            self.sc.axes.set_xlabel('День', fontsize=10)
            self.sc.axes.set_ylabel('Количество ЧС', fontsize=10)
        self.sc.figure.tight_layout()
        self.layout_for_graph.addWidget(self.sc)
        self.sc.draw()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = MainWindow()
    ex.show()
    sys.exit(app.exec())

Класс для встраивания графиков в PyQt

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import matplotlib.pyplot as plt
import numpy as np
import itertools


class MplCanvas(FigureCanvasQTAgg):
    def __init__(self, fig):
        self.fig = fig
        self.axes = self.fig.add_subplot(111)
        super(MplCanvas, self).__init__(self.fig)

    def plot_confusion_matrix(self, cm, classes,
                              normalize=False,
                              title='Confusion matrix',
                              cmap=plt.get_cmap('Blues')):
        self.axes.imshow(cm, interpolation='nearest', cmap=cmap)
        self.axes.set_title(title, fontsize=12)
        tick_marks = np.arange(len(classes))
        self.axes.set_xticks(tick_marks)
        self.axes.set_yticks(tick_marks)
        self.axes.set_xticklabels(classes, fontsize=10)
        self.axes.set_yticklabels(classes, fontsize=10)
        thresh = cm.max() / 2.
        for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
            self.axes.text(j, i, format(cm[i, j]),
                           horizontalalignment="center",
                           color="white" if cm[i, j] > thresh else "black")

Интерфейс, созданный в Qt Designer:

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(870, 693)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.btn_matrix = QtWidgets.QPushButton(self.centralwidget)
        self.btn_matrix.setObjectName("btn_matrix")
        self.gridLayout.addWidget(self.btn_matrix, 1, 0, 1, 1)
        self.btn_plot = QtWidgets.QPushButton(self.centralwidget)
        self.btn_plot.setObjectName("btn_plot")
        self.gridLayout.addWidget(self.btn_plot, 1, 1, 1, 1)
        self.layout_for_graph = QtWidgets.QVBoxLayout()
        self.layout_for_graph.setObjectName("layout_for_graph")
        self.gridLayout.addLayout(self.layout_for_graph, 0, 0, 1, 2)
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.btn_matrix.setText(_translate("MainWindow", "Матрица ошибок"))
        self.btn_plot.setText(_translate("MainWindow", "ЧС по часам"))

Ответы (1 шт):

Автор решения: Pak Uula

Спасибо, что дали пример. С ним получилось разобраться.

Barplot вытягивается вверх после того, как у объекта self.sc.axes выставляется параметр aspect в 1.0. По оси x координата меняется от -0.6 до 3.6, а по оси y от 0 до 9, поэтому график получается вытянутым вверх.

Чтобы график занимал всю площадь фигуры достаточно сбрасывать отношение осей после очистки графика:

    def graphic(self):
        self.sc.axes.clear()
        self.sc.axes.set_aspect('auto')
        # ...

Протестировано на вашем примере - работает.

→ Ссылка