Calculs & affichages / Matplotlib /
Matplotlib

Complément basique du poste de contrôle.


Avant-propos

Quelques exemples.
 


Description

Plan du tuto :
  1. Classe de base.
  2. Classe pour Matplotlib.
  3. Affichage d'une courbe.
  4. Refactoring.
  5. Animation : mise à jour de a courbe.
  6. Persistance de la géométrie.
  7. Événements clavier.
  8. Pilotage depuis le poste ce contrôle.

1 - Classe de base :
A la racine, créez le dossier show à l'intérieur duquel vous créerez le fichier show_base.py.

/show/show_base.py :

class ShowBase:
    """ Classe de base pour Matplotlib, Pyqtgraph, Bokeh, etc. """
    def __init__(self):
        pass

Pour l'instant vide, cette classe sera complétée au fur et à mesure.
 


2 - Classe pour Matplotlib

Retour au plan

Vérification.


3 - Affichage d'une courbe

Retour au plan


4 - Refactoring :

Retour au plan


5 - Animation : mise à jour de la courbe.

Retour au plan


6 - Persistance de la géométrie

Retour au plan

A faire, dans l'ordre :
  1. Ajouter ces 3 lignes (après super().__init__()) au début de la méthode __init__ :
            self.graph_name = sys.argv[1]
            self.node_id = int(sys.argv[2])
            self.parent_pid = int(sys.argv[3])
    

    sys.argv est une liste qui contient tous les arguments de la ligne de commande.
     

  2. Coder le chemin complet du node final, en ajoutant ces lignes dans le setup.
    Section """ Chemin complet du node final ... etc. """ ShowMatPlotLib.setup :
            """ Chemin complet du node final 'Plots' contenant les modèles de calculs. """
            bk_path = os.path.dirname(__file__).replace('show', 'backups')  # Dossier de backup.
            final_path = os.path.normpath(os.path.join(bk_path, self.graph_name, f'node{self.node_id}'))  # Node 'Plots'
            self.ut = Utils(final_path)
    

    Importer os et Utils (from functions.utils import Utils).
     

  3. Outre l'import de os et Utils, il faut aussi déclarer self.ut dans __init__ :
    ShowMatPlotLib.__init__ :
            self.ut = None
    

     

  4. Nous devons modifier l'instanciation de la classe Utils (Cette modification n'a aucune incidence sur le reste du code).
    utils.py > Utils.__init__ :
        def __init__(self, path=''):
            self.o_dt = DateTime()  # o_dt = 'Object DateTime'
            self.caller_dir = os.getcwd()  # dossier du code appelant. Ne pas remplacer getcwd()
            path_conf = self.caller_dir if path == '' else path
            self.settings = QSettings(f"{path_conf}{os.sep}params.conf", QSettings.IniFormat)
            self.watcher = None
    

     

  5. Créer un "surveillant", lancé par timer : la méthode listener.
    ShowMatPlotLib.listener :
        def listener(self):
            geometry = self.get_real_geometry()
            if self.geometry != geometry:
                self.ut.save_state(self.mgr.window)
                self.geometry = geometry
    
  6. Oui mais ... la méthode get_real_geometry ainsi que la propriété mgr n'existent pas. Créons-les.
    ShowMatPlotLib.get_real_geometry :
        def get_real_geometry(self):
            geom = self.mgr.window.geometry()
            return geom.x(), geom.y(), geom.width(), geom.height()
    

    @property ShowMatPlotLib.mgr :
        @property
        def mgr(self):
            return plt.get_current_fig_manager()
    

     

  7. L'attribut self.geometry doit être déclaré dans __init__ :
            self.geometry = None
    

     

  8. Créer le timer listen dans __init__.
    Ajouter cette ligne dans ShowMatPlotLib.__init__ :
            self.listen = QTimer()
    

    Ajouter la surveillance à la fréquence de 1 Hertz dans le setup.
    Section """ Événements. """  ShowMatPlotLib.setup :
            """ Événements. """
            self.listen.timeout.connect(self.listener)
            self.anim.timeout.connect(self.show_anim)
    

    Section """ Boucles. """  ShowMatPlotLib.setup :

            """ Boucles. """
            self.listen.start(1000)
            self.anim.start(self.delay)
            plt.show()
    

     

  9. Nous pouvons enfin, depuis le setup, lire l'état de la fenêtre au lancement et l'appliquer.
    Section """ Restauration de l'état de la fenêtre. """  ShowMatPlotLib.setup :
            """ Restauration de l'état de la fenêtre. """
            self.ut.restore_state(self.mgr.window)
    
  10. Vérifiez.

7 - Événements clavier

Retour au plan


8 - Pilotage depuis le poste de contrôle

Retour au plan

Souvenez-vous ... revenons au poste de contrôle.

Voila ce que nous voulons faire :


A faire, dans l'ordre.
  1. Affichez le poste de contrôle. Pour chaque afficheur, assurez-vous que Paramètres / Fenêtre / Interface est sur Matplotlib.
  2. Nous aurons besoin de connaître, en plusieurs endroits du code, les interfaces d'affichage (les scripts Python) présentes dans le projet.
    Par conséquent :
    • Déclarer l'attribut self.scripts dans __init__.
      Ajouter cette ligne dans plots.py > Node.__init__ :
              self.scripts = dict()
      

       
    • ... et l'affecter dans le setup.
      plots.py > Node.setup :
          def setup(self, child_file=__file__):
              self.o_grcontent = UiContentPlots(self)
              self.scripts = {
                  'Matplotlib': 'show_matplotlib.py',
                  'Pyqtgraph': 'show_pyqtgraph.py',
                  'Plotly': 'show_plotly.py',
                  'Bokeh': 'show_bokeh.py',
                  'Cufflinks': 'show_cufflinks.py',
                  'Dash': 'show_dash.py',
                  'Guiqwt': 'show_guiqwt.py',
                  'OpenCV': 'show_opencv.py',
                  'Seaborn': 'show_seaborn.py',
              }
              super().setup(child_file)
              self.init_calc()
      

       

  3. Les clés de ce dictionnaire sont les noms des différentes interfaces.
    Utilisons-les pour éviter la répétition de code. Modifions la méthode fixed_params.
    plots.py > Node.fixed_params :
        def fixed_params(self):
            """ Valeurs par défaut. """
            return {
                "Nombre d'entrées": [1, {'step': 1, 'limits': (1, 8)}],
                'Fenêtre': {
                    'Titre': 'Titre de la fenêtre',
                    'Interface': ['Matplotlib', {'values': list(self.scripts.keys())}]
                }
            }
    


     
  4. Le PID de l'affichage Matplotlib :
    • Pour améliorer les performances, la plupart des afficheurs seront lancés dans des process différents.
    • Windows étant multi-tâche, nous aurons plusieurs instances de Pyton.exe qui s'exécutent simultanément.
    • Toutefois, pour pouvoir interagir, le poste de contrôle doit connaitre les identifiants de chaque process d'affichage, les PID.
    • Il s'agit de l'attribut self.graph_pid, que l'on doit déclarer dans __init__, et affecter au moment du lancement du process.
      Ajouter cette ligne dans plots.py > Node.__init__ :
              self.graph_pid = None   # PID du process affichant le graphique.
      
  5. Surveillance de l'existence de la fenêtre d'affichage, après son lancement.
    • Si le PID ne répond plus, c'est parce que le process est mort (killed), autrement dit, que la fenêtre d'affichage a été fermée.
    • Dans ce cas, le libellé du bouton doit automatiquement revenir à 'Voir'.
    • Cette surveillance se fera dans un timer de période 3 secondes via l'attribut self.clock.
      Ajouter cette ligne dans plots.py > Node.__init__ :
              self.clock = QTimer()
      
      La classe QTimer (de QtCore) doit être importée.

      Insérer cette ligne avant self.script = ..., dans plots.py > Node.setup :
              self.clock.timeout.connect(self.detect_graph_closed)
      
    • La méthode appelée par ce timer est self.detect_graph_closed, elle n'existe pas. Créons-la.
      plots.py > Node.detect_graph_closed :
          def detect_graph_closed(self):
              if self.graph_pid is None or not psutil.pid_exists(self.graph_pid):
                  self.o_grcontent.button.setText('Voir')
                  self.clock.stop()
      

      Cette méthode rétablit le libellé du bouton à 'Voir' et arrête la surveillance.
      Installer puis importer psutil.

       

  6. La méthode plots.py > Node.show_figure devient :
        def show_figure(self):
            """ Désélection de tous les items de la scène, puis sélection de moi-même. """
            for item in self.o_scene.o_grscene.items():
                item.setSelected(False)
            self.o_grnode.setSelected(True)
    
            ui_fig = self.get_param(['Fenêtre', 'Interface'])
    
            # à coder ...

  7. Fermeture du poste de contrôle.
    • Chaque fenêtre d'affichage doit surveiller la présence du poste de contrôle.
    • Lorsque ce dernier ne répond plus, elle doit se fermer.
      # à coder ...

       


Vérification

  • Utiliser un graphe avec au moins 2 nodes de type Plots.
  • Vérifier que les nodes d'affichage ont pour interface Matplotlib (Dockable des paramètres).
  • Leurs boutons affichent 'Voir'.
  • Cliquer sur chacun d'eux, des graphiques Matplotlib indépendants apparaîssent.
    • Le libellé du bouton cliqué devient 'Fermer'.
  • Pour chacune de ces fenêtres, modifier la géométrie, la vitesse d'affichage, etc.
  • Les fermer par leur croix. Le bouton correspondant affiche 'Voir'.
  • Les fermer depuis les boutons lorsqu'ils affichent 'Fermer'.
  • Vérifier la persistance de la géométrie.
  • Vérifier que les fenêtres d'affichage sont vraiment indépendantes.
  • Fermer le poste de contrôle alors que des fenêtres d'affichage existent : elles doivent toutes se fermer.

Snippets

Essayez de résoudre cette fonctionnalité par vous-même.
Consultez les réponses (snippets) seulement si vous n'avez pas trop de temps.

Bonjour les codeurs !