Why Matplotlib.rcParams.update style do not update facecolor PyQt5?

Viewed 2141

I am using matplotlib styles and I try to change the styles dynamically when the style is chosen in the combobox. I am using Matplotlib 2.2.3, Python 3.6.6, PyQt5, Windows 10. But when I choose the dark_background style, the figure facecolor and the axes facecolor do not change. Here an animation: dark_bakground error This is the code:

File IHMDrawDates.py generated with pyuic5:

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MplMainWindow(object):
    def setupUi(self, MplMainWindow):
        MplMainWindow.setObjectName("MplMainWindow")
        MplMainWindow.resize(628, 416)
        self.centralwidget = QtWidgets.QWidget(MplMainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.mpl = MplWidgetTest(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.mpl.sizePolicy().hasHeightForWidth())
        self.mpl.setSizePolicy(sizePolicy)
        self.mpl.setObjectName("mpl")
        self.gridLayout_2.addWidget(self.mpl, 0, 0, 1, 1)
        self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
        self.groupBox.setMaximumSize(QtCore.QSize(95, 16777215))
        self.groupBox.setObjectName("groupBox")
        self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
        self.gridLayout.setObjectName("gridLayout")
        self.buttonDrawDate = QtWidgets.QPushButton(self.groupBox)
        self.buttonDrawDate.setMaximumSize(QtCore.QSize(75, 16777215))
        self.buttonDrawDate.setObjectName("buttonDrawDate")
        self.gridLayout.addWidget(self.buttonDrawDate, 1, 0, 1, 1)
        self.buttonErase = QtWidgets.QPushButton(self.groupBox)
        self.buttonErase.setMaximumSize(QtCore.QSize(75, 16777215))
        self.buttonErase.setObjectName("buttonErase")
        self.gridLayout.addWidget(self.buttonErase, 2, 0, 1, 1)
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.gridLayout.addItem(spacerItem, 3, 0, 1, 1)
        self.comboTema = QtWidgets.QComboBox(self.groupBox)
        self.comboTema.setObjectName("comboTema")
        self.gridLayout.addWidget(self.comboTema, 0, 0, 1, 1)
        self.gridLayout_2.addWidget(self.groupBox, 0, 1, 1, 1)
        MplMainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MplMainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 628, 21))
        self.menubar.setObjectName("menubar")
        MplMainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MplMainWindow)
        self.statusbar.setObjectName("statusbar")
        MplMainWindow.setStatusBar(self.statusbar)

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

    def retranslateUi(self, MplMainWindow):
        _translate = QtCore.QCoreApplication.translate
        MplMainWindow.setWindowTitle(_translate("MplMainWindow", "MainWindow"))
        self.groupBox.setTitle(_translate("MplMainWindow", "GroupBox"))
        self.buttonDrawDate.setText(_translate("MplMainWindow", "Draw"))
        self.buttonErase.setText(_translate("MplMainWindow", "Erase"))

from mplwidgettest import MplWidgetTest

mplwidgettest.py file that contains the method 'setTema' to update matplotlib's style. When I print the variable rcParams it contains axes.facecolor: black but it does not apply

from PyQt5.QtWidgets import QSizePolicy, QWidget, QVBoxLayout
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import (
    FigureCanvasQTAgg as FigureCanvas,
    NavigationToolbar2QT as NavigationToolbar)
import matplotlib as mplib


class MplCanvas(FigureCanvas):
    """Class to represent the FigureCanvas widget"""
    def __init__(self):
        # setup Matplotlib Figure and Axis
        mplib.rcParams.update(mplib.rcParamsDefault)
        mplib.style.use('bmh')
        self.fig = Figure()
        self.ax = self.fig.add_subplot(111)
        # initialization of the canvas
        FigureCanvas.__init__(self, self.fig)
        # we define the widget as expandable
        FigureCanvas.setSizePolicy(self,
                                   QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        # notify the system of updated policy
        FigureCanvas.updateGeometry(self)

    def setTema(self, tema='classic'):
        print(tema)
        mplib.style.use(tema)
        print(mplib.rcParams)
        mplib.rcParams.update(mplib.rcParams)


class MplWidgetTest(QWidget):
    """Widget defined in Qt Designer"""
    def __init__(self, parent=None):
        # initialization of Qt MainWindow widget
        QWidget.__init__(self, parent)
        # set the canvas to the Matplotlib widget
        self.canvas = MplCanvas()
        # create a NavigatioToolbar
        self.ntb = NavigationToolbar(self.canvas, self)
        # create a vertical box layout
        self.vbl = QVBoxLayout()
        # add mpl widget to vertical box
        self.vbl.addWidget(self.canvas)
        # add NavigationToolBar to vertical box
        self.vbl.addWidget(self.ntb)
        # set the layout to th vertical box
        self.setLayout(self.vbl)

mainMplWidget.py File that calls the two previous files, and contains the method 'cambiarTema' that changes the matplotlib's style

import sys
from IHMDrawDates import Ui_MplMainWindow
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QCursor
import numpy as np


class DesignerMainWindow(QMainWindow, Ui_MplMainWindow):

    def __init__(self, parent=None):
        super(DesignerMainWindow, self).__init__(parent)
        self.setupUi(self)
        self.posX = []
        self.posY = []
        temas = ['bmh', 'classic', 'grayscale', 'seaborn-bright',
                 'ggplot', 'dark_background']
        self.comboTema.addItems(temas)
        # connect the signals with the slots
        self.buttonDrawDate.clicked.connect(self.drawDate)
        self.buttonErase.clicked.connect(self.eraseDate)
        self.comboTema.currentIndexChanged.connect(self.cambiarTema)

    def cambiarTema(self):
        tema = self.comboTema.currentText()
        self.mpl.canvas.setTema(str(tema))
        self.mpl.canvas.ax.clear()
        self.mpl.canvas.draw()
        self.mpl.canvas.flush_events()

    def drawDate(self):
        x = np.arange(0, 100, 0.1)
        y = np.sin(x)
        self.mpl.canvas.ax.plot(x, y)
        self.mpl.canvas.ax.relim()
        self.mpl.canvas.ax.autoscale(True)
        self.mpl.ntb.update()
        self.mpl.ntb.push_current()
        self.mpl.canvas.draw()

    def eraseDate(self):
        self.mpl.canvas.ax.clear()
        self.mpl.ntb.update()
        self.mpl.ntb.push_current()
        self.mpl.canvas.draw()

if __name__ == '__main__':
    app = 0
    app = QApplication(sys.argv)
    dmw = DesignerMainWindow()
    # show it
    dmw.show()
    sys.exit(app.exec_())
2 Answers

The following is an explanation why setting a new style will not update the existing figure.

The matplotlib.rcParams is essentially a dictionary which stores certain default parameters that are meant to be used when creating objects in matplotlib. rcParams.update will update this dictionary; update is a method of the python dict object. matplotlib.style.use is a shortcut to updating the rcParams, it will load the respective parameters from a file or dictionary.

Those parameters are then used when a new object is created. In a simplified fashion this would look like

def create_object(arg1, parameter1=None):
    if not parameter1:
        # use the default from the rcParams
        parameter1 = rcParams["parameter1"]
    obj = MatplotlibObject(arg1, parameter1=parameter1)
    return obj

Such function is used when creating objects. However, once such object is created, it will not be run again when something in rcParams changes.

obj = create_object(1)
rcParams.update({"parameter1" : "New Value"})
# at this point, obj would not know about the new parameter1 value

As commented already, there are two options.

  • Change the parameter manually. You may change the parameter manually after the object has been created.

    obj = create_object(1)
    obj.set_parameter1("New Value")
    
  • Recreate the object. You may remove the existing object, update the rcParams and recreate the object such that it'll use the new value.

    obj = create_object(1)
    del obj
    rcParams.update({"parameter1" : "New Value"})
    obj = create_object(1)
    

The only solution I have found so far is to add these two lines in the 'setTema' method of the file mplwidgettest.py

self.fig.set_facecolor(mplib.rcParams['figure.facecolor'])
self.ax.set_facecolor(mplib.rcParams['axes.facecolor'])

The complete class is:

class MplCanvas(FigureCanvas):
    """Class to represent the FigureCanvas widget"""
    def __init__(self):
        # setup Matplotlib Figure and Axis
        mplib.rcParams.update(mplib.rcParamsDefault)
        mplib.style.use('bmh')
        self.fig = Figure()
        self.ax = self.fig.add_subplot(111)
        # initialization of the canvas
        FigureCanvas.__init__(self, self.fig)
        # we define the widget as expandable
        FigureCanvas.setSizePolicy(self,
                                   QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        # notify the system of updated policy
        FigureCanvas.updateGeometry(self)

    def setTema(self, tema='classic'):
        mplib.rcParams.update(mplib.rcParamsDefault)
        mplib.style.use(tema)
        self.fig.set_facecolor(mplib.rcParams['figure.facecolor'])
        self.ax.set_facecolor(mplib.rcParams['axes.facecolor'])

An animation of the result: matplot dark style

However, I do not think that each of the attributes of the figure has to be changed, if they are supposed to be defined in the style. Blessed matplotlib :-(

Related