Salut à tous !

Je travaille sur une application réalisant notamment de la visualisation de données. Pour cela, j’utilise Matplotlib. Parfois, je dois effacer la figure pour en afficher une autre. Dans un tel cas, j’ai un problème : la place de la précédente échelle de couleur (colorbar) reste vide, le tracé de la figure ne se faisant plus dans l’intégralité de la fenêtre. J’ai réalisé un exemple minimum complet montrant mon problème :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
 
"""
Exemple de mise à jour d’une figure 2D avec Matplotlib et PyQt5.
 
:module: figureRefresh
:author: Yoann LE BARS
:version: 1.0
:date: 30/08/2018
"""
 
import sys
from PyQt5.QtWidgets import (
        QApplication, QWidget, QVBoxLayout, QSizePolicy, QPushButton
)
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
import random
 
class FenetrePrincipale (QWidget):
    """
    Classe décrivant la fenêtre principale.
    """
 
    def _trace (self):
        """
        Réalise la figure.
        """
 
        # Valeurs à tracer.
        Z = np.sin(8. * self._X) * np.sin(8. * self._Y) \
            * random.uniform(-1., 1.)
 
        # Valeur minimale à tracer.
        minimum = min(Z.flatten())
        # Valeur maximale à tracer.
        maximum = max(Z.flatten())
        # Pas de l’échelle des valeurs.
        pas = (maximum - minimum) / 30.
        # Échelle des valeurs.
        echelle = np.arange(minimum - pas, maximum + pas, pas)
 
        # Contours à tracer.
        cf = self._axe.contourf(self._X, self._Y, Z, levels = echelle)
        # Barre de légende.
        self._cBar = self._figure.colorbar(cf, ax = self._axe,
                                           format = '%.2f')
 
        self._figure.canvas.draw()
 
    def _retrace (self):
        """
        Action pour relancer le tracé.
        """
 
        self._cBar.remove()
        self._axe.cla()
        self._trace()
 
    def __init__ (self):
        """
        Initialiseur de la classe.
 
        :param parent: Parent de l’objet instancié.
        :param **kwargs: Arguments de la ligne de commande.
        """
 
        QWidget.__init__(self)
 
        # Espace principal de la fenêtre.
        espPrinc = QVBoxLayout(self)
 
        # Définition de la figure à tracer.
        self._figure, self._axe = plt.subplots()
        zoneTrace = FigureCanvas(self._figure)
        zoneTrace.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        zoneTrace.updateGeometry()
        espPrinc.addWidget(zoneTrace)
 
        # Boutton pour relancer le tracé.
        retraceBoutton = QPushButton('Retracer')
        retraceBoutton.clicked.connect(self._retrace)
        espPrinc.addWidget(retraceBoutton)
 
        # Variable intermédiaire pour la définition de la grille.
        x = np.linspace(0, 1, 100)
        # Définition de la grille.
        self._X, self._Y = np.meshgrid(x, x)
 
        self._trace()
 
    def __enter__ (self):
        """
        Retourne l’instance courrante.
        """
 
        return self
 
    def __exit__ (self, exc_type, exc_val, exc_tb):
        """
        Désallocation des ressources.
 
        :param exc_type: Si une exception a été lancée, son type.
        :param exc_val: Valeur de l’exception.
        :param exc_tb: Trace d’execution au moment de l’exception.
        """
 
        pass
 
if __name__ == '__main__':
    # Instance de l’application.
    app = QApplication(sys.argv)
    # Fenêtre principale de l’application.
    with FenetrePrincipale() as fenPrinc:
        fenPrinc.show()
        sys.exit(app.exec_())
En lançant ce code, on voit bien qu’à chaque fois que l’on appuie sur le bouton, la zone de tracé devient plus petite, ne s’étendant pas sur l’espace de la précédente échelle de couleur.

Ce code est en Python 3 et utilise PyQt5 (afin de réaliser un bouton de mise à jour), mais je pense vraiment que mon problème provient purement de mon utilisation de Matplotlib. En définitive, mon problème est : comment puis-je supprimer la barre de légende (colorbar) sans laisser un espace vide dans la zone de tracé ?

Dans le cas particulier que je présente ici, dans la méthode « _retrace » de la classe « FenetrePrincipale », la ligne « self._axe.cla() » n’est pas indispensable. Cependant, dans les cas concrets d’utilisations de mon application, elle est nécessaire, aussi l’ai-je conservé dans ce code d’exemple. De toute façon, avec ou sans cette ligne, le problème persiste. Également, nous sommes d’accord que la figure tracée ici n’est pas d’un grand intérêt, c’est simplement pour réaliser un exemple qui ne nécessite aucun fichier par ailleurs.

Donc, quelqu’un a-t-il déjà été en mesure de mettre à jour une figure avec Matplotlib sans obtenir un vide à la place de l’ancienne barre de légende ? Cela fait quelque temps que je cherche sur le net et j’ai fait plusieurs essais (je vous ai mis ici ce que j’ai de plus concluant), mais pas moyen de résoudre ce problème.

À bientôt.