View  Edit  Attributes  History  Attach  Print  Search

PYQTLAB

PyQt Lab' : Graphiques Math : Pyqtgraph : Afficher une courbe dans un graphique Pyqtgraph à partir d'une formule saisie dans un champ texte et enregistrement dans un fichier.

Par X. HINAULT - Juin 2013

Ce que l'on va faire ici

  • Afficher une courbe dans un graphique Pyqtgraph à partir d'une formule saisie dans un champ texte et enregistrement dans un fichier. On peut ainsi paramétrer en direct la courbe plutôt que de relancer l'exécution du code à chaque fois...
  • Ce code se base sur la fonction Python eval() qui permet d'interpréter une chaîne texte comme du code Python.

Pré-requis

  • python 2.7
  • pyqt4.x
  • pyqtgraph

Le fichier d'interface *.ui


<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>720</width>
    <height>465</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>PyQt + pyqtgraph : Affichage courbe à partir fichier texte</string>
  </property>
  <widget class="PlotWidget" name="graph">
   <property name="geometry">
    <rect>
     <x>5</x>
     <y>10</y>
     <width>480</width>
     <height>360</height>
    </rect>
   </property>
  </widget>
  <widget class="QLineEdit" name="lineEditChemin">
   <property name="geometry">
    <rect>
     <x>5</x>
     <y>385</y>
     <width>421</width>
     <height>23</height>
    </rect>
   </property>
  </widget>
  <widget class="QTextEdit" name="textEdit">
   <property name="geometry">
    <rect>
     <x>490</x>
     <y>10</y>
     <width>216</width>
     <height>361</height>
    </rect>
   </property>
   <property name="font">
    <font>
     <stylestrategy>PreferAntialias</stylestrategy>
    </font>
   </property>
   <property name="lineWrapMode">
    <enum>QTextEdit::NoWrap</enum>
   </property>
  </widget>
  <widget class="QPushButton" name="pushButtonEnregistrer">
   <property name="geometry">
    <rect>
     <x>620</x>
     <y>380</y>
     <width>86</width>
     <height>27</height>
    </rect>
   </property>
   <property name="text">
    <string>Enregistrer</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pushButtonNouveau">
   <property name="geometry">
    <rect>
     <x>435</x>
     <y>380</y>
     <width>86</width>
     <height>27</height>
    </rect>
   </property>
   <property name="text">
    <string>Nouveau</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pushButtonOuvrir">
   <property name="geometry">
    <rect>
     <x>530</x>
     <y>380</y>
     <width>85</width>
     <height>27</height>
    </rect>
   </property>
   <property name="text">
    <string>Ouvrir</string>
   </property>
  </widget>
  <widget class="QPushButton" name="pushButtonCalculer">
   <property name="geometry">
    <rect>
     <x>470</x>
     <y>420</y>
     <width>126</width>
     <height>27</height>
    </rect>
   </property>
   <property name="text">
    <string>Calculer</string>
   </property>
  </widget>
  <widget class="QLabel" name="label">
   <property name="geometry">
    <rect>
     <x>10</x>
     <y>415</y>
     <width>51</width>
     <height>13</height>
    </rect>
   </property>
   <property name="text">
    <string>x =</string>
   </property>
  </widget>
  <widget class="QLabel" name="label_2">
   <property name="geometry">
    <rect>
     <x>230</x>
     <y>415</y>
     <width>51</width>
     <height>13</height>
    </rect>
   </property>
   <property name="text">
    <string>y =</string>
   </property>
  </widget>
  <widget class="QTextEdit" name="textEditY">
   <property name="geometry">
    <rect>
     <x>255</x>
     <y>415</y>
     <width>191</width>
     <height>41</height>
    </rect>
   </property>
  </widget>
  <widget class="QTextEdit" name="textEditX">
   <property name="geometry">
    <rect>
     <x>30</x>
     <y>415</y>
     <width>191</width>
     <height>41</height>
    </rect>
   </property>
  </widget>
 </widget>
 <customwidgets>
  <customwidget>
   <class>PlotWidget</class>
   <extends>QGraphicsView</extends>
   <header>pyqtgraph</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

 

Le fichier d'interface *.py

  • Fichier obtenu automatiquement avec l'utilitaire pyuic4 à partir du fichier *.ui créé avec QtDesigner :

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

# Form implementation generated from reading ui file
#
# Created: Wed Jun  5 16:42:28 2013
#      by: PyQt4 UI code generator 4.9.1
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName(_fromUtf8("Form"))
        Form.resize(720, 465)
        self.graph = PlotWidget(Form)
        self.graph.setGeometry(QtCore.QRect(5, 10, 480, 360))
        self.graph.setObjectName(_fromUtf8("graph"))
        self.lineEditChemin = QtGui.QLineEdit(Form)
        self.lineEditChemin.setGeometry(QtCore.QRect(5, 385, 421, 23))
        self.lineEditChemin.setObjectName(_fromUtf8("lineEditChemin"))
        self.textEdit = QtGui.QTextEdit(Form)
        self.textEdit.setGeometry(QtCore.QRect(490, 10, 216, 361))
        font = QtGui.QFont()
        font.setStyleStrategy(QtGui.QFont.PreferAntialias)
        self.textEdit.setFont(font)
        self.textEdit.setLineWrapMode(QtGui.QTextEdit.NoWrap)
        self.textEdit.setObjectName(_fromUtf8("textEdit"))
        self.pushButtonEnregistrer = QtGui.QPushButton(Form)
        self.pushButtonEnregistrer.setGeometry(QtCore.QRect(620, 380, 86, 27))
        self.pushButtonEnregistrer.setObjectName(_fromUtf8("pushButtonEnregistrer"))
        self.pushButtonNouveau = QtGui.QPushButton(Form)
        self.pushButtonNouveau.setGeometry(QtCore.QRect(435, 380, 86, 27))
        self.pushButtonNouveau.setObjectName(_fromUtf8("pushButtonNouveau"))
        self.pushButtonOuvrir = QtGui.QPushButton(Form)
        self.pushButtonOuvrir.setGeometry(QtCore.QRect(530, 380, 85, 27))
        self.pushButtonOuvrir.setObjectName(_fromUtf8("pushButtonOuvrir"))
        self.pushButtonCalculer = QtGui.QPushButton(Form)
        self.pushButtonCalculer.setGeometry(QtCore.QRect(470, 420, 126, 27))
        self.pushButtonCalculer.setObjectName(_fromUtf8("pushButtonCalculer"))
        self.label = QtGui.QLabel(Form)
        self.label.setGeometry(QtCore.QRect(10, 415, 51, 13))
        self.label.setObjectName(_fromUtf8("label"))
        self.label_2 = QtGui.QLabel(Form)
        self.label_2.setGeometry(QtCore.QRect(230, 415, 51, 13))
        self.label_2.setObjectName(_fromUtf8("label_2"))
        self.textEditY = QtGui.QTextEdit(Form)
        self.textEditY.setGeometry(QtCore.QRect(255, 415, 191, 41))
        self.textEditY.setObjectName(_fromUtf8("textEditY"))
        self.textEditX = QtGui.QTextEdit(Form)
        self.textEditX.setGeometry(QtCore.QRect(30, 415, 191, 41))
        self.textEditX.setObjectName(_fromUtf8("textEditX"))

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

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQt + pyqtgraph : Affichage courbe à partir fichier texte", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonEnregistrer.setText(QtGui.QApplication.translate("Form", "Enregistrer", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonNouveau.setText(QtGui.QApplication.translate("Form", "Nouveau", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonOuvrir.setText(QtGui.QApplication.translate("Form", "Ouvrir", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonCalculer.setText(QtGui.QApplication.translate("Form", "Calculer", None, QtGui.QApplication.UnicodeUTF8))
        self.label.setText(QtGui.QApplication.translate("Form", "x =", None, QtGui.QApplication.UnicodeUTF8))
        self.label_2.setText(QtGui.QApplication.translate("Form", "y =", None, QtGui.QApplication.UnicodeUTF8))

from pyqtgraph import PlotWidget

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    Form = QtGui.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())



 

Le fichier d'application *Main.py


#!/usr/bin/python
# -*- coding: utf-8 -*-

# par X. HINAULT - Mai 2013 - Tous droits réservés
# GPLv3 - www.mon-club-elec.fr

# modules a importer
from PyQt4.QtGui import *
from PyQt4.QtCore import *  # inclut QTimer..
import os,sys

import pyqtgraph as pg # pour accès à certaines constantes pyqtgraph, widget, etc...

import numpy as np # math et tableaux

from tuto_pyqt_pyqtgraph_courbe_fichier_save_load_eval import * # fichier obtenu à partir QtDesigner et pyuic4

# +/- variables et objets globaux

class myApp(QWidget, Ui_Form): # la classe reçoit le Qwidget principal ET la classe définie dans test.py obtenu avec pyuic4
        def __init__(self, parent=None):
                QWidget.__init__(self) # initialise le Qwidget principal
                self.setupUi(parent) # Obligatoire

                # --- Variables de classe

                # --- Paramétrage des widgets de l'interface GUI si nécessaire ---

                # --- Connexions entre signaux des widgets et fonctions
                # connecte chaque signal utilisé des objets à l'appel de la fonction voulue

                self.connect(self.pushButtonOuvrir, SIGNAL("clicked()"), self.pushButtonOuvrirClicked)  # connecte le signal Clicked de l'objet à l'appel de la fonction voulue
                self.connect(self.pushButtonEnregistrer, SIGNAL("clicked()"), self.pushButtonEnregistrerClicked) # connecte le signal Clicked de l'objet bouton à l'appel de la fonction voulue
                self.connect(self.pushButtonNouveau, SIGNAL("clicked()"), self.pushButtonNouveauClicked) # connecte le signal Clicked de l'objet bouton à l'appel de la fonction voulue

                self.connect(self.pushButtonCalculer, SIGNAL("clicked()"), self.pushButtonCalculerClicked) # connecte le signal Clicked de l'objet bouton à l'appel de la fonction voulue

                # --- Code actif initial  ---

                #-- initialise le graphique pyqtgraph --
                # l'objet self.graph correspond au plotWidget créé dans QtDesigner

                # aspect fond /axes
                #self.graph.hideAxis('left') # masque axes - ‘left’, ‘bottom’, ‘right’, or ‘top’               
                self.graph.setBackgroundBrush(QBrush(QColor(Qt.white))) # la classe PlotWidget est un GraphicsWidget qui est un QGraphics View
                self.graph.showGrid(x=True, y=True)  # affiche la grille
                self.graph.getAxis('bottom').setPen(pg.mkPen(0,0,255)) # couleur de l'axe + grille
                self.graph.getAxis('left').setPen(pg.mkPen(255,0,0)) # couleur de l'axe + grille

                # légende des axes
                labelStyle = {'color': '#00F', 'font-size': '10pt'} # propriétés CSS à utiliser pour le label
                self.graph.getAxis('bottom').setLabel('X', units='unit', **labelStyle) # label de l'axe
                self.graph.getAxis('left').setLabel('Y', units='unit', **labelStyle) # label de l'axe

                # adaptation échelle axes
                # axe X et Y sont autoscale par défaut
                #self.graph.enableAutoRange(axis=pg.ViewBox.YAxis, enable=False) # fonction plotItem : désactive autoscale Y

                #self.minY=-1
                #self.maxY=1           
                #self.graph.setYRange(self.minY,self.maxY) # fonction plotItem : fixe échelle des Y

                # interactivité
                #self.graph.setInteractive(False) # fonction QGraphics View : pour inactiver interaction souris
                self.graph.getViewBox().setMouseMode(pg.ViewBox.RectMode)  # fonction ViewBox pas accessible depuis PlotWidget : fixe selection par zone
                self.graph.setMouseEnabled(x=False, y=True) # désactive interactivité axe X

                #-- affichage de la courbe vide --
                self.courbe=self.graph.plot(pen=(0,0,255)) # avec couleur

        # --- les fonctions appelées, utilisées par les signaux des widgets ---

        def pushButtonOuvrirClicked(self):
                print("Bouton <Sélectionner Fichier> appuyé")

                # ouvre fichier en tenant compte du chemin déjà saisi dans le champ
                if self.lineEditChemin.text()=="":
                        self.filename=QFileDialog.getOpenFileName(self, 'Ouvrir fichier', os.getenv('HOME')) # ouvre l'interface fichier - home par défaut
                        #self.filename=QFileDialog.getOpenFileName(self, 'Ouvrir fichier', QDir.currentPath()) # ouvre l'interface fichier - chemin courant par défaut
                else:
                        info=QFileInfo(self.lineEditChemin.text()) # définit un objet pour manipuler info sur fichier à partir chaine champ
                        print info.absoluteFilePath() # debug  
                        self.filename=QFileDialog.getOpenFileName(self, 'Ouvrir fichier', info.absoluteFilePath()) # ouvre l'interface fichier - à partir chemin

                print(self.filename) # affiche le chemin obtenu dans la console
                self.lineEditChemin.setText(self.filename) # affiche le chemin obtenu dans le champ texte


                #-- ouverture du fichier Ui et récupération du contenu
                myFile=open(self.filename,"r") # ouvre le fichier en lecture
                myFileContent=myFile.read() # lit le contenu du fichier
                myFile.close() # ferme le fichier - tant que le fichier reste ouvert, il est inacessible à d'autres ressources

                self.textEdit.setText(myFileContent) # copie le contenu dans la zone texte

                # extraction des valeurs à partir du fichier
                # numpy.loadtxt(fname, dtype=<type 'float'>, comments='#', delimiter=None, converters=None, skiprows=0, usecols=None, unpack=False, ndmin=0)
                data=np.loadtxt(str(self.filename), delimiter=",") # on attend des données séparées par , et un saut de ligne après chaque donnée x,y
                print data

                self.x =data[:,0] # extrait 1ère colonne
                print self.x

                self.y=data[:,1] # extrait 2ème colonne
                print self.y

                self.courbe.setData(self.x, self.y) # met à jour la courbe




        def pushButtonNouveauClicked(self):
                print("Bouton NOUVEAU appuyé")

                # ouvre fichier en tenant compte du chemin déjà saisi dans le champ
                if self.lineEditChemin.text()=="":
                        self.filename=QFileDialog.getSaveFileName(self, 'Nouveau fichier', os.getenv('HOME')) # ouvre l'interface fichier - home par défaut
                        #self.filename=QFileDialog.getOpenFileName(self, 'Ouvrir fichier', QDir.currentPath()) # ouvre l'interface fichier - chemin courant par défaut
                else:
                        info=QFileInfo(self.lineEditChemin.text()) # définit un objet pour manipuler info sur fichier à partir chaine champ
                        print info.absoluteFilePath() # debug  
                        self.filename=QFileDialog.getSaveFileName(self, 'Nouveau fichier', info.absoluteFilePath()) # ouvre l'interface fichier - à partir chemin

                print(self.filename)
                self.lineEditChemin.setText(self.filename)


        def pushButtonEnregistrerClicked(self):        
                print("Bouton <ENREGISTRE> appuyé")                    

                if self.lineEditChemin.text()!="":
                        #self.myFile = open(self.filename, 'a') # ouverture du fichier en mode écriture append
                        self.myFile = open(self.filename, 'w') # ouverture du fichier en mode écriture write - efface contenu existant
                        # open est une fonction du langage python : http://docs.python.org/2/library/functions.html#open
                        # mode peut-être r, w, a (append)              
                        self.myFile.write(str(self.textEdit.toPlainText())) # écrit les données dans le fichier        
                        self.myFile.close() # ferme le fichier         

                        # extraction des valeurs à partir du fichier
                        # numpy.loadtxt(fname, dtype=<type 'float'>, comments='#', delimiter=None, converters=None, skiprows=0, usecols=None, unpack=False, ndmin=0)
                        data=np.loadtxt(str(self.filename), delimiter=",") # on attend des données séparées par , et un saut de ligne après chaque donnée x,y
                        print data

                        self.x =data[:,0] # extrait 1ère colonne
                        print self.x

                        self.y=data[:,1] # extrait 2ème colonne
                        print self.y

                        self.courbe.setData(self.x, self.y) # met à jour la courbe

        def pushButtonCalculerClicked(self):           
                print("Bouton <CALCULER> appuyé")      

                print (self.textEditX.toPlainText()) # contenu de la zone texte X
                x=eval(str(self.textEditX.toPlainText())) # exécute le contenu de la zone texte X
                print x
                self.x=x
                # on utilise x quand même pour utilisation directe dans formules texte

                print (self.textEditY.toPlainText()) # contenu de la zone texte X
                self.y=eval(str(self.textEditY.toPlainText())) # exécute le contenu de la zone texte X
                print self.y

                # affiche courbe
                self.courbe.setData(self.x, self.y) # met à jour la courbe

                # formatage données pour copie dans texteEdit sous la forme x,y
                data=np.zeros((len(x),2)) # crée tableau vide 2D de même taille que x
                data[:,0]=self.x
                data[:,1]=self.y

                print data

                # ajoute les données au texte edit
                self.textEdit.setText("")# RAZ le textEdit
                for line in data:
                        self.textEdit.append(str(line[0])+","+str(line[1])) # ajoute les données au textEdit


        # --- les fonctions appelées, utilisées par les signaux hors widgets ---

        # --- fonctions de classes autres---    

# -- Autres Classes utiles --

# -- Classe principale (lancement)  --
def main(args):
        a=QApplication(args) # crée l'objet application
        f=QWidget() # crée le QWidget racine
        c=myApp(f) # appelle la classe contenant le code de l'application
        f.show() # affiche la fenêtre QWidget
        r=a.exec_() # lance l'exécution de l'application
        return r

if __name__=="__main__": # pour rendre le code exécutable
        main(sys.argv) # appelle la fonction main

 

Utilisation

  • Les 2 fichiers suivants sont à enregistrer dans un même répertoire, l'un en nom.py et l'autre en nomMain.py.
  • Puis lancer l'application depuis Geany ou équivalent, en exécutant le fichier nomMain.py
  • La molette de souris assure le zoom
  • Saisir du code pour définir le tableau x et le tableau y comme vous le feriez dans le code lui-même :
    • np.arange(0.0, 361.0, 1.0)
    • np.sin(np.radians(self.x))