0

I am building an application in PyQt5. I want to use seaborn to visualize a matrix and update the heatmap created by seaborn when the matrix data is changed. I create the original plot like this:

from matplotlib.figure import Figure
import numpy as np
import seaborn as sns
class MplWidget_pcolormesh(QWidget):

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.canvas = FigureCanvas(Figure())
        self.clb = []
        self.plot = []


        vertical_layout = QVBoxLayout()
        vertical_layout.addWidget(self.canvas)

        self.axes = self.canvas.figure.add_subplot(111)
        self.setLayout(vertical_layout)
        self.canvas.toolbar = NavigationToolbar(self.canvas, self)
        self.layout().addWidget(self.canvas.toolbar)
        self.layout().addWidget(self.canvas)
        X = np.array([[0, 1], [1, 0]])
        self.plot = sns.heatmap(X, cmap='PuBu', square=True, linewidth=0.1, linecolor=(0.1, 0.2, 0.2),
                                ax=self.axes, vmin=0, vmax=1)

This only creates a simple heatmap plot that is later updated:
Simple heatmap to begin with:
Simple heatmap to begin with

Then, in another py file I want to update it like this:

def matrixPlot(self, selected):
    X = ... # generating new data
    self.view.MplWidget.axes.clear()
    self.view.MplWidget.plot = sns.heatmap(X, cmap='PuBu', square=True, linewidth=0.1, linecolor=(0.1, 0.2, 0.2),
                            ax=self.view.MplWidget.axes, vmin=0, vmax=1)
    self.view.MplWidget.canvas.draw()

It does update the plot, but it always regenerates the colorbar:

heatmap after a few updates:
heatmap after a few updates

Now I tried to save the axis objects, so I can just update them, but when I try creating the subplot like this:

fig, (ax, cbarax) = self.axes = self.canvas.figure.add_subplot(111)

I get the error:

TypeError: cannot unpack non-iterable AxesSubplot object

How could I create the heatmap in a way, that I can update the content and the colorbar values later, without creating a number of colorbars?


EDIT (@eyllanesc):
You can get the same result by building this project:

simpleMatrixGUI.py

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'simpleMatrixGUI.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.MplWidget = MplWidget(self.centralwidget)
        self.MplWidget.setObjectName("MplWidget")
        self.verticalLayout.addWidget(self.MplWidget)
        self.randMatrixGenerator = QtWidgets.QPushButton(self.centralwidget)
        self.randMatrixGenerator.setObjectName("randMatrixGenerator")
        self.verticalLayout.addWidget(self.randMatrixGenerator)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

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

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.randMatrixGenerator.setText(_translate("MainWindow", "Generate new"))
from mplwidget import MplWidget

Windows.py

from PyQt5.QtWidgets import QMainWindow

from simpleMatrixGUI import Ui_MainWindow


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

Main.py

import sys
from PyQt5.QtWidgets import QApplication

from MainPresenter import MainPresenter
from Windows import MainWindow


if __name__ == '__main__':
    # Create the application (with optional system arguments)
    app = QApplication(sys.argv)

    # Create model, view and presenter objects
    model = None
    view = MainWindow()
    presenter = MainPresenter(view=view, model=model)

    # Start the main app event loop
    exit_code = app.exec_()

    # Perform system exit to safely quit (relay the app exit code)
    sys.exit(exit_code)

mplwidget.py

from PyQt5.QtWidgets import *
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as
        FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
import numpy as np
import seaborn as sns


class MplWidget(QWidget):

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.canvas = FigureCanvas(Figure())
        self.clb = []
        self.plot = []


        vertical_layout = QVBoxLayout()
        vertical_layout.addWidget(self.canvas)

        self.axes = self.canvas.figure.add_subplot(111)
        self.setLayout(vertical_layout)
        self.canvas.toolbar = NavigationToolbar(self.canvas, self)
        self.layout().addWidget(self.canvas.toolbar)
        self.layout().addWidget(self.canvas)
        X = np.random.randn(10, 8)
        self.plot = sns.heatmap(X, cmap='PuBu', square=True, linewidth=0.1, linecolor=(0.1, 0.2, 0.2),
                                ax=self.axes, vmin=np.min(X), vmax=np.max(X))

MainPresenter.py

from typing import Optional
import numpy as np
from Windows import MainWindow
import seaborn as sns


class MainPresenter:
    def __init__(self, view: MainWindow, model: Optional[int] = None):
        self.view = view
        self.model = model

        self.view.show()

        view.randMatrixGenerator.clicked.connect(self.matrixPlot)

    def matrixPlot(self, selected):
        X = np.random.randn(10,8)
        self.view.MplWidget.axes.clear()
        self.view.MplWidget.plot = sns.heatmap(X, cmap='PuBu', square=True, linewidth=0.1, linecolor=(0.1, 0.2, 0.2),
                                ax=self.view.MplWidget.axes, vmin=np.min(X), vmax=np.max(X))
        self.view.MplWidget.canvas.draw()
Zephyr
  • 11,891
  • 53
  • 45
  • 80
Victor Y.
  • 23
  • 6

1 Answers1

0

You could create the colorbar axis in this way:

grid_kws = {'width_ratios': (0.9, 0.05), 'wspace': 0.2}
fig, (ax, cbar_ax) = plt.subplots(1, 2, gridspec_kw = grid_kws, figsize = (10, 8))

Then, you can pass cbar_ax to sns.heatmap:

sns.heatmap(ax = ax, cbar_ax = cbar_ax, ...)

Have a look to this answer for a reference.


EDIT

If you apply the concepts above to your files, you need to edit them in this way:

mplwidget.py

from PyQt5.QtWidgets import *
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as
        FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt


class MplWidget(QWidget):

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        grid_kws = {'width_ratios': (0.9, 0.05), 'wspace': 0.2}
        self.figure, (self.axes, self.cbar_ax) = plt.subplots(1, 2, gridspec_kw = grid_kws, figsize = (10, 8))

        self.canvas = FigureCanvas(self.figure)
        self.clb = []
        self.plot = []


        vertical_layout = QVBoxLayout()
        vertical_layout.addWidget(self.canvas)

        self.setLayout(vertical_layout)
        self.canvas.toolbar = NavigationToolbar(self.canvas, self)
        self.layout().addWidget(self.canvas.toolbar)
        self.layout().addWidget(self.canvas)
        X = np.random.randn(10, 8)
        self.plot = sns.heatmap(X, cmap='PuBu', square=True, linewidth=0.1, linecolor=(0.1, 0.2, 0.2),
                                ax=self.axes, vmin=np.min(X), vmax=np.max(X), cbar_ax = self.cbar_ax)

MainPresenter.py

from typing import Optional
import numpy as np
from Windows import MainWindow
import seaborn as sns


class MainPresenter:
    def __init__(self, view: MainWindow, model: Optional[int] = None):
        self.view = view
        self.model = model


        self.view.show()

        view.randMatrixGenerator.clicked.connect(self.matrixPlot)

    def matrixPlot(self, selected):
        X = np.random.randn(10,8)
        self.view.MplWidget.axes.clear()
        self.view.MplWidget.plot = sns.heatmap(X, cmap='PuBu', square=True, linewidth=0.1, linecolor=(0.1, 0.2, 0.2),
                                ax=self.view.MplWidget.axes, vmin=np.min(X), vmax=np.max(X), cbar_ax = self.view.MplWidget.cbar_ax)
        self.view.MplWidget.canvas.draw()

enter image description here

Zephyr
  • 11,891
  • 53
  • 45
  • 80
  • Yes, I saw that solution, and I have tried it. If I use `fig, (ax, cbar_ax) = plt.subplots(1, 2, gridspec_kw = grid_kws, figsize = (10, 8))` to create the subplot, then it pops up in a new window instead being plotted on the figureCanvas object on my GUI interface – Victor Y. Aug 24 '21 at 10:37
  • 1
    Updated my answer based on files you provided – Zephyr Aug 24 '21 at 11:50