View  Edit  Attributes  History  Attach  Print  Search

PYQTLAB

PyQt Lab' : Graphiques Math : Pyqtgraph : Affichage progressif de courbe dans un graphique pyqtgraph avec bouton de stop, effacer, widget LCD de valeur courante et croix de ligne de sélection en mode stoppé.

Par X. HINAULT - Aout 2013

Ce que l'on va faire ici

  • Affichage progressif de courbe dans un graphique pyqtgraph avec bouton de stop, effacer, widget LCD de valeur courante et croix de ligne de sélection en mode stoppé.

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>658</width>
    <height>418</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>PyQt + pyqtgraph : Affichage courbe progressive avec stop, lcdNumber</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="lineEditValeurMin">
   <property name="geometry">
    <rect>
     <x>500</x>
     <y>80</y>
     <width>66</width>
     <height>23</height>
    </rect>
   </property>
   <property name="text">
    <string>-1000</string>
   </property>
  </widget>
  <widget class="QLabel" name="labelUnite">
   <property name="geometry">
    <rect>
     <x>595</x>
     <y>130</y>
     <width>51</width>
     <height>13</height>
    </rect>
   </property>
   <property name="text">
    <string>Unité :</string>
   </property>
  </widget>
  <widget class="QLCDNumber" name="lcdNumberValeurBrute">
   <property name="geometry">
    <rect>
     <x>495</x>
     <y>25</y>
     <width>96</width>
     <height>36</height>
    </rect>
   </property>
   <property name="styleSheet">
    <string notr="true">background-color: rgb(170, 255, 127);
color: rgb(0, 170, 0);</string>
   </property>
   <property name="smallDecimalPoint">
    <bool>false</bool>
   </property>
  </widget>
  <widget class="QLabel" name="labelValeurCalc">
   <property name="geometry">
    <rect>
     <x>500</x>
     <y>110</y>
     <width>106</width>
     <height>16</height>
    </rect>
   </property>
   <property name="text">
    <string>Valeur calculée :</string>
   </property>
  </widget>
  <widget class="QLineEdit" name="lineEditValeurMax">
   <property name="geometry">
    <rect>
     <x>575</x>
     <y>80</y>
     <width>66</width>
     <height>23</height>
    </rect>
   </property>
   <property name="text">
    <string>+1000</string>
   </property>
  </widget>
  <widget class="QLabel" name="labelDelaiMesure">
   <property name="geometry">
    <rect>
     <x>510</x>
     <y>265</y>
     <width>71</width>
     <height>16</height>
    </rect>
   </property>
   <property name="text">
    <string>delai ms</string>
   </property>
  </widget>
  <widget class="QLabel" name="labelValeurBrute">
   <property name="geometry">
    <rect>
     <x>495</x>
     <y>10</y>
     <width>81</width>
     <height>16</height>
    </rect>
   </property>
   <property name="text">
    <string>Valeur brute :</string>
   </property>
  </widget>
  <widget class="QLabel" name="labelValeurMin">
   <property name="geometry">
    <rect>
     <x>500</x>
     <y>65</y>
     <width>71</width>
     <height>16</height>
    </rect>
   </property>
   <property name="text">
    <string>Valeur mini</string>
   </property>
  </widget>
  <widget class="QLCDNumber" name="lcdNumberValeurCalc">
   <property name="geometry">
    <rect>
     <x>495</x>
     <y>130</y>
     <width>96</width>
     <height>36</height>
    </rect>
   </property>
   <property name="styleSheet">
    <string notr="true">background-color: rgb(255, 255, 0);
color: rgb(255, 0, 0);</string>
   </property>
   <property name="smallDecimalPoint">
    <bool>true</bool>
   </property>
   <property name="mode">
    <enum>QLCDNumber::Dec</enum>
   </property>
  </widget>
  <widget class="QDial" name="dialDelaiMesure">
   <property name="geometry">
    <rect>
     <x>505</x>
     <y>175</y>
     <width>50</width>
     <height>64</height>
    </rect>
   </property>
   <property name="minimum">
    <number>0</number>
   </property>
   <property name="maximum">
    <number>8</number>
   </property>
   <property name="value">
    <number>4</number>
   </property>
   <property name="notchesVisible">
    <bool>true</bool>
   </property>
  </widget>
  <widget class="QPushButton" name="pushRadioButtonStop">
   <property name="geometry">
    <rect>
     <x>575</x>
     <y>240</y>
     <width>66</width>
     <height>27</height>
    </rect>
   </property>
   <property name="text">
    <string>STOP</string>
   </property>
   <property name="checkable">
    <bool>true</bool>
   </property>
  </widget>
  <widget class="QLabel" name="labelValeurMax">
   <property name="geometry">
    <rect>
     <x>575</x>
     <y>65</y>
     <width>71</width>
     <height>16</height>
    </rect>
   </property>
   <property name="text">
    <string>Valeur maxi</string>
   </property>
  </widget>
  <widget class="QLineEdit" name="lineEditUnite">
   <property name="geometry">
    <rect>
     <x>595</x>
     <y>145</y>
     <width>56</width>
     <height>23</height>
    </rect>
   </property>
   <property name="text">
    <string>U</string>
   </property>
  </widget>
  <widget class="QLCDNumber" name="lcdNumberDelaiMesure">
   <property name="geometry">
    <rect>
     <x>500</x>
     <y>240</y>
     <width>64</width>
     <height>23</height>
    </rect>
   </property>
   <property name="styleSheet">
    <string notr="true">background-color: rgb(255, 255, 147);
color: rgb(0, 85, 255);</string>
   </property>
   <property name="midLineWidth">
    <number>0</number>
   </property>
   <property name="segmentStyle">
    <enum>QLCDNumber::Flat</enum>
   </property>
   <property name="value" stdset="0">
    <double>0.000000000000000</double>
   </property>
  </widget>
  <widget class="QPushButton" name="pushButtonEffacer">
   <property name="geometry">
    <rect>
     <x>575</x>
     <y>195</y>
     <width>66</width>
     <height>27</height>
    </rect>
   </property>
   <property name="text">
    <string>Effacer</string>
   </property>
  </widget>
  <widget class="QCheckBox" name="checkBoxLabelAxisLeft">
   <property name="geometry">
    <rect>
     <x>195</x>
     <y>395</y>
     <width>96</width>
     <height>19</height>
    </rect>
   </property>
   <property name="text">
    <string>Label Axe Y</string>
   </property>
   <property name="checked">
    <bool>true</bool>
   </property>
  </widget>
  <widget class="QCheckBox" name="checkBoxAxisLeft">
   <property name="geometry">
    <rect>
     <x>195</x>
     <y>375</y>
     <width>82</width>
     <height>19</height>
    </rect>
   </property>
   <property name="text">
    <string>Axe Y</string>
   </property>
   <property name="checked">
    <bool>true</bool>
   </property>
  </widget>
  <widget class="QCheckBox" name="checkBoxLabelAxisBottom">
   <property name="geometry">
    <rect>
     <x>95</x>
     <y>395</y>
     <width>96</width>
     <height>19</height>
    </rect>
   </property>
   <property name="text">
    <string>Label Axe X</string>
   </property>
   <property name="checked">
    <bool>true</bool>
   </property>
  </widget>
  <widget class="QPushButton" name="pushButtonInit">
   <property name="geometry">
    <rect>
     <x>5</x>
     <y>380</y>
     <width>85</width>
     <height>27</height>
    </rect>
   </property>
   <property name="text">
    <string>Initialiser</string>
   </property>
  </widget>
  <widget class="QCheckBox" name="checkBoxAxisBottom">
   <property name="geometry">
    <rect>
     <x>95</x>
     <y>375</y>
     <width>82</width>
     <height>19</height>
    </rect>
   </property>
   <property name="text">
    <string>Axe X</string>
   </property>
   <property name="checked">
    <bool>true</bool>
   </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: Sat Aug 24 10:43:45 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(658, 418)
        self.graph = PlotWidget(Form)
        self.graph.setGeometry(QtCore.QRect(5, 10, 480, 360))
        self.graph.setObjectName(_fromUtf8("graph"))
        self.lineEditValeurMin = QtGui.QLineEdit(Form)
        self.lineEditValeurMin.setGeometry(QtCore.QRect(500, 80, 66, 23))
        self.lineEditValeurMin.setObjectName(_fromUtf8("lineEditValeurMin"))
        self.labelUnite = QtGui.QLabel(Form)
        self.labelUnite.setGeometry(QtCore.QRect(595, 130, 51, 13))
        self.labelUnite.setObjectName(_fromUtf8("labelUnite"))
        self.lcdNumberValeurBrute = QtGui.QLCDNumber(Form)
        self.lcdNumberValeurBrute.setGeometry(QtCore.QRect(495, 25, 96, 36))
        self.lcdNumberValeurBrute.setStyleSheet(_fromUtf8("background-color: rgb(170, 255, 127);\n"
"color: rgb(0, 170, 0);"))
        self.lcdNumberValeurBrute.setSmallDecimalPoint(False)
        self.lcdNumberValeurBrute.setObjectName(_fromUtf8("lcdNumberValeurBrute"))
        self.labelValeurCalc = QtGui.QLabel(Form)
        self.labelValeurCalc.setGeometry(QtCore.QRect(500, 110, 106, 16))
        self.labelValeurCalc.setObjectName(_fromUtf8("labelValeurCalc"))
        self.lineEditValeurMax = QtGui.QLineEdit(Form)
        self.lineEditValeurMax.setGeometry(QtCore.QRect(575, 80, 66, 23))
        self.lineEditValeurMax.setObjectName(_fromUtf8("lineEditValeurMax"))
        self.labelDelaiMesure = QtGui.QLabel(Form)
        self.labelDelaiMesure.setGeometry(QtCore.QRect(510, 265, 71, 16))
        self.labelDelaiMesure.setObjectName(_fromUtf8("labelDelaiMesure"))
        self.labelValeurBrute = QtGui.QLabel(Form)
        self.labelValeurBrute.setGeometry(QtCore.QRect(495, 10, 81, 16))
        self.labelValeurBrute.setObjectName(_fromUtf8("labelValeurBrute"))
        self.labelValeurMin = QtGui.QLabel(Form)
        self.labelValeurMin.setGeometry(QtCore.QRect(500, 65, 71, 16))
        self.labelValeurMin.setObjectName(_fromUtf8("labelValeurMin"))
        self.lcdNumberValeurCalc = QtGui.QLCDNumber(Form)
        self.lcdNumberValeurCalc.setGeometry(QtCore.QRect(495, 130, 96, 36))
        self.lcdNumberValeurCalc.setStyleSheet(_fromUtf8("background-color: rgb(255, 255, 0);\n"
"color: rgb(255, 0, 0);"))
        self.lcdNumberValeurCalc.setSmallDecimalPoint(True)
        self.lcdNumberValeurCalc.setMode(QtGui.QLCDNumber.Dec)
        self.lcdNumberValeurCalc.setObjectName(_fromUtf8("lcdNumberValeurCalc"))
        self.dialDelaiMesure = QtGui.QDial(Form)
        self.dialDelaiMesure.setGeometry(QtCore.QRect(505, 175, 50, 64))
        self.dialDelaiMesure.setMinimum(0)
        self.dialDelaiMesure.setMaximum(8)
        self.dialDelaiMesure.setProperty("value", 4)
        self.dialDelaiMesure.setNotchesVisible(True)
        self.dialDelaiMesure.setObjectName(_fromUtf8("dialDelaiMesure"))
        self.pushRadioButtonStop = QtGui.QPushButton(Form)
        self.pushRadioButtonStop.setGeometry(QtCore.QRect(575, 240, 66, 27))
        self.pushRadioButtonStop.setCheckable(True)
        self.pushRadioButtonStop.setObjectName(_fromUtf8("pushRadioButtonStop"))
        self.labelValeurMax = QtGui.QLabel(Form)
        self.labelValeurMax.setGeometry(QtCore.QRect(575, 65, 71, 16))
        self.labelValeurMax.setObjectName(_fromUtf8("labelValeurMax"))
        self.lineEditUnite = QtGui.QLineEdit(Form)
        self.lineEditUnite.setGeometry(QtCore.QRect(595, 145, 56, 23))
        self.lineEditUnite.setObjectName(_fromUtf8("lineEditUnite"))
        self.lcdNumberDelaiMesure = QtGui.QLCDNumber(Form)
        self.lcdNumberDelaiMesure.setGeometry(QtCore.QRect(500, 240, 64, 23))
        self.lcdNumberDelaiMesure.setStyleSheet(_fromUtf8("background-color: rgb(255, 255, 147);\n"
"color: rgb(0, 85, 255);"))
        self.lcdNumberDelaiMesure.setMidLineWidth(0)
        self.lcdNumberDelaiMesure.setSegmentStyle(QtGui.QLCDNumber.Flat)
        self.lcdNumberDelaiMesure.setProperty("value", 0.0)
        self.lcdNumberDelaiMesure.setObjectName(_fromUtf8("lcdNumberDelaiMesure"))
        self.pushButtonEffacer = QtGui.QPushButton(Form)
        self.pushButtonEffacer.setGeometry(QtCore.QRect(575, 195, 66, 27))
        self.pushButtonEffacer.setObjectName(_fromUtf8("pushButtonEffacer"))
        self.checkBoxLabelAxisLeft = QtGui.QCheckBox(Form)
        self.checkBoxLabelAxisLeft.setGeometry(QtCore.QRect(195, 395, 96, 19))
        self.checkBoxLabelAxisLeft.setChecked(True)
        self.checkBoxLabelAxisLeft.setObjectName(_fromUtf8("checkBoxLabelAxisLeft"))
        self.checkBoxAxisLeft = QtGui.QCheckBox(Form)
        self.checkBoxAxisLeft.setGeometry(QtCore.QRect(195, 375, 82, 19))
        self.checkBoxAxisLeft.setChecked(True)
        self.checkBoxAxisLeft.setObjectName(_fromUtf8("checkBoxAxisLeft"))
        self.checkBoxLabelAxisBottom = QtGui.QCheckBox(Form)
        self.checkBoxLabelAxisBottom.setGeometry(QtCore.QRect(95, 395, 96, 19))
        self.checkBoxLabelAxisBottom.setChecked(True)
        self.checkBoxLabelAxisBottom.setObjectName(_fromUtf8("checkBoxLabelAxisBottom"))
        self.pushButtonInit = QtGui.QPushButton(Form)
        self.pushButtonInit.setGeometry(QtCore.QRect(5, 380, 85, 27))
        self.pushButtonInit.setObjectName(_fromUtf8("pushButtonInit"))
        self.checkBoxAxisBottom = QtGui.QCheckBox(Form)
        self.checkBoxAxisBottom.setGeometry(QtCore.QRect(95, 375, 82, 19))
        self.checkBoxAxisBottom.setChecked(True)
        self.checkBoxAxisBottom.setObjectName(_fromUtf8("checkBoxAxisBottom"))

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

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQt + pyqtgraph : Affichage courbe progressive avec stop, lcdNumber", None, QtGui.QApplication.UnicodeUTF8))
        self.lineEditValeurMin.setText(QtGui.QApplication.translate("Form", "-1000", None, QtGui.QApplication.UnicodeUTF8))
        self.labelUnite.setText(QtGui.QApplication.translate("Form", "Unité :", None, QtGui.QApplication.UnicodeUTF8))
        self.labelValeurCalc.setText(QtGui.QApplication.translate("Form", "Valeur calculée :", None, QtGui.QApplication.UnicodeUTF8))
        self.lineEditValeurMax.setText(QtGui.QApplication.translate("Form", "+1000", None, QtGui.QApplication.UnicodeUTF8))
        self.labelDelaiMesure.setText(QtGui.QApplication.translate("Form", "delai ms", None, QtGui.QApplication.UnicodeUTF8))
        self.labelValeurBrute.setText(QtGui.QApplication.translate("Form", "Valeur brute :", None, QtGui.QApplication.UnicodeUTF8))
        self.labelValeurMin.setText(QtGui.QApplication.translate("Form", "Valeur mini", None, QtGui.QApplication.UnicodeUTF8))
        self.pushRadioButtonStop.setText(QtGui.QApplication.translate("Form", "STOP", None, QtGui.QApplication.UnicodeUTF8))
        self.labelValeurMax.setText(QtGui.QApplication.translate("Form", "Valeur maxi", None, QtGui.QApplication.UnicodeUTF8))
        self.lineEditUnite.setText(QtGui.QApplication.translate("Form", "U", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonEffacer.setText(QtGui.QApplication.translate("Form", "Effacer", None, QtGui.QApplication.UnicodeUTF8))
        self.checkBoxLabelAxisLeft.setText(QtGui.QApplication.translate("Form", "Label Axe Y", None, QtGui.QApplication.UnicodeUTF8))
        self.checkBoxAxisLeft.setText(QtGui.QApplication.translate("Form", "Axe Y", None, QtGui.QApplication.UnicodeUTF8))
        self.checkBoxLabelAxisBottom.setText(QtGui.QApplication.translate("Form", "Label Axe X", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonInit.setText(QtGui.QApplication.translate("Form", "Initialiser", None, QtGui.QApplication.UnicodeUTF8))
        self.checkBoxAxisBottom.setText(QtGui.QApplication.translate("Form", "Axe X", 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_timer_courbe_button_lcd_croix import * # fichier obtenu à partir QtDesigner et pyuic4

# +/- variables et objets globaux

#-- variables globales
resolution=1.0 # nombre de valeurs intermédiaires

flagMouseClicked=False # drapeau temoin clic souris sur graphique (en mode stoppé)

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

                # réglages graphiques
                self.connect(self.pushButtonInit, SIGNAL("clicked()"), self.pushButtonInitClicked)
                self.connect(self.checkBoxAxisBottom, SIGNAL("clicked()"), self.checkBoxAxisBottomClicked) # connecte le signal Clicked de l'objet checkBox à l'appel de la fonction voulue
                self.connect(self.checkBoxAxisLeft, SIGNAL("clicked()"), self.checkBoxAxisLeftClicked) # connecte le signal Clicked de l'objet checkBox à l'appel de la fonction voulue
                self.connect(self.checkBoxLabelAxisBottom, SIGNAL("clicked()"), self.checkBoxLabelAxisBottomClicked) # connecte le signal Clicked de l'objet checkBox à l'appel de la fonction voulue
                self.connect(self.checkBoxLabelAxisLeft, SIGNAL("clicked()"), self.checkBoxLabelAxisLeftClicked) # connecte le signal Clicked de l'objet checkBox à l'appel de la fonction voulue

                # reglages oscillo
                self.connect(self.pushButtonEffacer, SIGNAL("clicked()"), self.pushButtonEffacerClicked) # connecte le signal Clicked de l'objet bouton à l'appel de la fonction voulue
                self.connect(self.pushRadioButtonStop, SIGNAL("clicked()"), self.pushRadioButtonStopClicked) # connecte le signal Clicked de l'objet bouton à l'appel de la fonction voulue
                self.connect(self.dialDelaiMesure, SIGNAL("valueChanged(int)"), self.dialDelaiMesureChanged) # connecte le signal Clicked de l'objet bouton à l'appel de la fonction voulue
                self.pushRadioButtonStop.setStyleSheet(QString.fromUtf8("background-color: rgb(0, 255, 0);")) # bouton de stop en vert au depart

                #-- pour délais de mesure en ms
                self.delaisMesure=[1,2,4,8,16,32,64,128,256] # en ms
                self.interval=self.delaisMesure[4] # intervalle entre  2 valeurs - en ms
                self.lcdNumberDelaiMesure.display(self.delaisMesure[4]) # valeur initiale pour le délai

                # --- 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.Ymin=-1
                self.Ymax=1            
                self.graph.setYRange(self.Ymin,self.Ymax) # fonction plotItem : fixe échelle des Y

                self.Xmin=0
                self.Xmax=360  
                self.graph.setXRange(self.Xmin,self.Xmax) # fonction plotItem : fixe échelle des X

                #-- champs valeurs
                self.lineEditValeurMin.setText(str(self.Ymin)) # par défaut valeur Y
                self.lineEditValeurMax.setText(str(self.Ymax)) # par défaut valeur 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

                #-- initialise données --
                #-- définition des x
                #self.nombreValeurs=360
                #self.x = np.arange(0.0, self.nombreValeurs+1, 1.0) # crée un vecteur de n valeurs à intervalle régulier pour les x
                #print(self.x) # debug - affiche les valeurs x

                #-- calcul des y : courbe y=f(x)
                #self.y=np.sin(np.radians(self.x))# crée un tableau de valeur y basé sur x - courbe y=sin(x)
                #self.y= np.random.normal(0,1,size=self.nombreValeurs) # génére une série de 1000 valeurs aléatoires

                #print(self.y) # debug - affiche les valeurs y

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

                self.compt=0 # variable comptage

                #-- point de sélection
                self.pointSelect=np.array([[0,0]]) # tableau de 1 point
                #self.courbe2=self.graph.plot(self.pointSelect[:,0],self.pointSelect[:,1],pen=(0,0,255),symbolBrush=(255,0,0),symbolPen='r') # avac paramétrage symboles (parmi o, s, t, d, +) etc..
                # NB : les x : pointSelect[:,0], les y : pointSelect[:,1]
                # voir bouton stop

                #-- lignes sélection --
                self.vLine = pg.InfiniteLine(angle=90, movable=False) # crée une ligne inifinie
                self.vLine.setPen(pg.mkPen(0,0,0)) # couleur de la ligne
                #self.graph.addItem(self.vLine, ignoreBounds=True) # ajoute la ligne au graphique - fonction ViewBox accessible depuis PlotItem
                # voir bouton stop

                self.hLine = pg.InfiniteLine(angle=0, movable=False) # crée une ligne inifinie
                self.hLine.setPen(pg.mkPen(0,0,0)) # couleur de la ligne
                #self.graph.addItem(self.hLine, ignoreBounds=True) # ajoute la ligne au graphique - fonction ViewBox accessible depuis PlotItem
                # voir bouton stop

                self.vb=self.graph.getViewBox() # récupère l'objet viewbox du graphique pour accès aux fonctions utiles
                print self.vb

                # connexion signal mouvement de la souris
                #proxy = pg.SignalProxy(self.graph.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved) # proxy implémente objet commun de gestion des signaux
                self.graph.scene().sigMouseMoved.connect(self.mouseMoved) # connecte le signal souris bouge à la fonction voulue

                self.graph.scene().sigMouseClicked.connect(self.mouseClicked) # connecte le signal souris bouge à la fonction voulue


                #-- initialisation du Timer
                self.timer=QTimer() # déclare un timer Qt
                self.timer.start(self.interval) # lance le timer - durée en ms
                self.connect(self.timer, SIGNAL("timeout()"), self.timerEvent) # connecte le signal timeOut de l'objet timer à l'appel de la fonction voulue
                # NB : le nom de la fonction appelée est ici timerEvent : ce nom est arbitraire et peut être ce que l'on veut...


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

        #--- si modif delai mesure --
        def dialDelaiMesureChanged(self, valeur):
                print("Dial Delai Mesure modifié : " + str(valeur) + " | délai = " + str(self.delaisMesure[valeur]) + " ms")
                self.lcdNumberDelaiMesure.display(self.delaisMesure[valeur]) # utilise la valeur [i] pour le délai

                self.interval=self.delaisMesure[valeur]
                self.timer.setInterval(self.interval) # modifie le nouvel intervalle du timer

                if self.pushRadioButtonStop.isChecked()is not True: # si le bouton stop est pas enfoncé
                        self.timer.start() # redémarre le timer


        #---- bouton STOP -------
        def pushRadioButtonStopClicked(self):
                print("Bouton Stop appuyé")
                print("Etat bouton Stop =" + str(self.pushRadioButtonStop.isChecked()))
                if self.pushRadioButtonStop.isChecked(): # si bouton enfoncé
                        self.pushRadioButtonStop.setStyleSheet(QString.fromUtf8("background-color: rgb(255, 0, 0);"))
                        self.timer.stop() # stop le timer

                        #-- met à jour position des lignes --
                        self.vLine.setPos(self.pointSelect[:,0])
                        self.hLine.setPos(self.pointSelect[:,1])

                        # activation tracé croix
                        self.courbe2=self.graph.plot(self.pointSelect[:,0],self.pointSelect[:,1],pen=(0,0,255),symbolBrush=(255,0,0),symbolPen='r') # avac paramétrage symboles (parmi o, s, t, d, +) etc..
                        self.graph.addItem(self.vLine, ignoreBounds=True) # ajoute la ligne au graphique - fonction ViewBox accessible depuis PlotItem
                        self.graph.addItem(self.hLine, ignoreBounds=True) # ajoute la ligne au graphique - fonction ViewBox accessible depuis PlotItem

                        # réactivation suivi pointeur via flag global témoin clic souris
                        global flagMouseClicked
                        flagMouseClicked=False # suivi actif curseur sera actif si flag False

                else:

                        # désactivation tracé croix
                        self.graph.removeItem(self.courbe2)# enlève le plotItemData du plotItem
                        self.graph.removeItem(self.vLine) # enlève la ligne au graphique - fonction ViewBox accessible depuis PlotItem
                        self.graph.removeItem(self.hLine) # enlève la ligne au graphique - fonction ViewBox accessible depuis PlotItem

                        self.pushRadioButtonStop.setStyleSheet(QString.fromUtf8("background-color: rgb(0, 255, 0);"))
                        self.timer.start() # redémarre le timer

        #--- bouton effacer --
        def pushButtonEffacerClicked(self):
                print("Bouton Effacer cliqué")
                #self.timer.stop() # arret timer
                self.compt=0 # réinitialise comptage valeurs et donc graphique...
                self.timerEvent() # appelle la fonction timerEvent pour prise en compte immédiate

#======== réglages graphique ===========
        # pushButton
        def pushButtonInitClicked(self):
                print("Bouton Init cliqué")
                self.graph.setYRange(self.Ymin,self.Ymax) # fonction plotItem : fixe échelle des Y
                self.graph.setXRange(self.Xmin,self.Xmax) # fonction plotItem : fixe échelle des X

        def checkBoxAxisBottomClicked(self):
                print("Checkbox axe X cliqué")

                if self.checkBoxAxisBottom.isChecked(): # si checkBox sélectionné
                        print("Etat checkBox = " + str(self.checkBoxAxisBottom.isChecked()))
                        self.graph.showAxis('bottom')
                        # self.graph.showAxis('bottom', True) # équivalent
                        if self.checkBoxLabelAxisBottom.isChecked(): self.graph.showLabel('bottom',True)        
                else : # si checkBox pas sélectionné
                        print("Etat checkBox = " + str(self.checkBoxAxisBottom.isChecked()))
                        self.graph.hideAxis('bottom')
                        # self.graph.showAxis('bottom', False) # équivalent
                        self.graph.showLabel('bottom',False)

        def checkBoxAxisLeftClicked(self):
                print("Checkbox axe Y cliqué")

                if self.checkBoxAxisLeft.isChecked(): # si checkBox sélectionné
                        print("Etat checkBox = " + str(self.checkBoxAxisLeft.isChecked()))
                        self.graph.showAxis('left')
                        # self.graph.showAxis('left', True) # équivalent
                        if self.checkBoxLabelAxisLeft.isChecked():self.graph.showLabel('left',True)                    
                else : # si checkBox pas sélectionné
                        print("Etat checkBox = " + str(self.checkBoxAxisLeft.isChecked()))
                        self.graph.hideAxis('left')
                        self.graph.showLabel('left',False)

        def checkBoxLabelAxisBottomClicked(self):
                print("Checkbox Label axe X cliqué")

                if self.checkBoxLabelAxisBottom.isChecked(): # si checkBox sélectionné
                        print("Etat checkBox = " + str(self.checkBoxLabelAxisBottom.isChecked()))
                        self.graph.showLabel('bottom',True)    
                else : # si checkBox pas sélectionné
                        print("Etat checkBox = " + str(self.checkBoxLabelAxisBottom.isChecked()))
                        self.graph.showLabel('bottom',False)

        def checkBoxLabelAxisLeftClicked(self):
                print("Checkbox Label axe Y cliqué")

                if self.checkBoxLabelAxisLeft.isChecked(): # si checkBox sélectionné
                        print("Etat checkBox = " + str(self.checkBoxLabelAxisLeft.isChecked()))
                        self.graph.showLabel('left',True)                      
                else : # si checkBox pas sélectionné
                        print("Etat checkBox = " + str(self.checkBoxLabelAxisLeft.isChecked()))
                        self.graph.showLabel('left',False)


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

        #-- fonction gestion survenue évènement Timer
        def timerEvent(self): # fonction appelée lors de la survenue d'un évènement Timer - nom de la fonction indiférrent
                #print("Timer")

                if self.compt==0: # premier passage
                        self.points= np.array([[self.compt,self.compt]]) # tableau à 2 dimensions - ici 1er points
                        newY=np.sin(np.radians(self.compt)) * np.cos(np.radians(3*self.compt))
                        #newY=analogRead(A2)
                        self.x=self.points[:,0] # la première colonne = les x
                        self.y=self.points[:,1] # la deuxième colonne = les y          
                        self.compt=self.compt+1 # incrémente compt
                elif self.compt<=self.Xmax: # on remplit le tableau de point une première fois
                        newY=np.sin(np.radians(self.compt)) * np.cos(np.radians(3*self.compt))
                        #newY=self.compt # x=y - debug
                        #newY=analogRead(A2)
                        self.points=np.append(self.points,[[self.compt,newY]],axis=0)# ajouter un point au tableau
                        self.x=self.points[:,0] # la première colonne = les x
                        self.y=self.points[:,1] # la deuxième colonne = les y          
                        self.compt=self.compt+1 # incrémente compt
                else:
                        #self.points=roll(self.points,1, axis=1) # décale les éléments y de 1 - fonctin numpy
                        #self.x=self.points[:,0] # la première colonne = les x - existe déjà
                        #self.y=self.points[:,1] # la deuxième colonne = les y - existe déjà

                        #self.y=roll(self.y,1) # décale les éléments y de 1 - fonctin numpy - les x ne bougent pas..
                        self.y=np.roll(self.y,-1) # décale les éléments y de 1 - fonction numpy - les x ne bougent pas.. et remet y[0] en y[max].. évite recalcul...
                        newY=self.y[len(self.y)-1] # derniere valeur du tableau y
                        #self.y[self.Xmax-1]=self.y[self.Xmax-1]/2 # nouvelle valeur en dernière position
                        #self.y[self.Xmax]=0 # nouvelle valeur en dernière position
                        #self.y[self.Xmax]=np.sin(np.radians(self.compt)) * np.cos(np.radians(3*self.compt)) # avec recalcul
                        #self.compt=self.compt+1 # incrémente compt

                        # important : pour le tracé, ce n'est pas l'ordre qui est important, mais la coordonnée x,y dans notre cas...

                # remarquer la simplicité avec laquelle il es possible d'extraire le tableau des x ou des y à partir du tableau de points


                #if self.compt==self.Xmax : self.compt=0 # RAZ compt

                # debug
                #print self.points
                #print self.x  
                #print self.y

                """
                #-- initialise données --
                self.x = arange(0.0, 361.0, 1.0) # crée un vecteur de n valeurs à intervalle régulier pour les x
                print(self.x) # debug - affiche les valeurs x

                #self.y = zeros(len(self.x), Float) # debug - crée un tableau rempli de 0 de la taille voulue pour les y       
                #-- calcul des y : courbe y=sin(x).cos(3x)
                self.y=sin(radians(self.x)) * cos(radians(3*self.x))# crée un tableau de valeur y basé sur x
                print(self.y) # debug - affiche les valeurs y
                """

                #-- met à jour leslcdNumber
                # actualise lcdNumber
                self.lcdNumberValeurBrute.display(newY) # affiche la valeur

                # équiv map : interp(256,[1,512],[5,10]) (package numpy)
                self.valeurCalc=np.interp(newY, [-1,1],[float(self.lineEditValeurMin.text()),float(self.lineEditValeurMax.text())]) # équiv map
                self.lcdNumberValeurCalc.display(self.valeurCalc)

                #-- initialise la courbe --
                self.courbe.setData(self.x,self.y) # initialisation valeurs courbe


        # fonction de gestion des mouvements souris - fonction appelée à partir pg.SignalProxy
        #def mouseMoved(self, evt):
        def mouseMoved(self, pos): # si connexion directe du signal "mouseMoved" : la fonction reçoit le point courant
                print ("Mouse moved")
                #pos = evt[0]  ## using signal proxy turns original arguments into a tuple
                print pos

                #pos=evt.pos()
                if self.graph.sceneBoundingRect().contains(pos): # si le point est dans la zone courante
                        mousePoint = self.vb.mapSceneToView(pos) # récupère le point souris à partir ViewBox
                        index = int(mousePoint.x()*resolution) # en fonction de l'intervalle arange x

                        #-- mise à jour position point courant courbe
                        if (index > 0 and index < len(self.x)-1  # si on est entre 0 et taille de X
                                and self.pushRadioButtonStop.isChecked() # et si stop appuyé
                                and flagMouseClicked==False):  # et si flag témoin clic souris = False
                                pointSelect=np.array([[mousePoint.x(),self.y[index+1]]]) # tableau de 1 point = le point courant
                                #pointSelect=np.array([[mousePoint.x(),mousePoint.y()]]) # tableau de 1 point = le point courant
                                self.courbe2.setData(pointSelect[:,0],pointSelect[:,1]) # met à jour le point courant
                                #self.label.setText("<span style='font-size: 10pt'>x=%0.1f,   <span style='color: red'>y1=%0.1f</span>" % (mousePoint.x(), self.y[index+1]))

                                #self.labelX.setText("X = "+str(int(mousePoint.x())))
                                #self.labelY.setText("Y = "+ str(self.y[index+1]))

                                #-- met à jour leslcdNumber
                                # actualise lcdNumber
                                self.lcdNumberValeurBrute.display(self.y[index+1]) # affiche la valeur

                                # équiv map : interp(256,[1,512],[5,10]) (package numpy)
                                self.valeurCalc=np.interp(self.y[index+1], [-1,1],[float(self.lineEditValeurMin.text()),float(self.lineEditValeurMax.text())]) # équiv map
                                self.lcdNumberValeurCalc.display(self.valeurCalc)

                                #-- met à jour position des lignes --
                                self.vLine.setPos(mousePoint.x())
                                #self.hLine.setPos(mousePoint.y())
                                self.hLine.setPos(self.y[index+1]) # point sur la courbe


        def mouseClicked(self, evt): # si connexion directe du signal "mouseClicked" : la fonction reçoit l'evenement
                print ("Mouse clicked")

                # ici gestion du clic souris
                print evt
                #evt recu est un MouseClickEvent qui dispose des fonctions suivantes :
                # http://www.pyqtgraph.org/documentation/graphicsscene/mouseclickevent.html#mouseclickevent

                if self.pushRadioButtonStop.isChecked() and evt.button()==1: # si mode stop actif et si bouton gauche
                        global flagMouseClicked
                        flagMouseClicked=not flagMouseClicked # inverse le drapeau
                        # si drapeau False, le suivi pointeur actif, si True, restera au point courant

                        # MAJ des lignes

                        # récupère position dans la scene
                        pos=evt.scenePos() # position dans la scene - voir : http://www.pyqtgraph.org/documentation/graphicsscene/mouseclickevent.html
                        print pos

                        #print self.graph.sceneBoundingRect().contains(pos) # debug

                        if self.graph.sceneBoundingRect().contains(pos): # si le point est dans la zone courante
                                print "Point dans la zone de dessin"
                                mousePoint = self.vb.mapSceneToView(pos) # récupère le point souris à partir ViewBox
                                index = int(mousePoint.x()*resolution) # en fonction de l'intervalle arange x

                                #-- mise à jour position point courant et des lignes courbe
                                if index > 0 and index < len(self.x)-1: # si on est entre 0 et taille de X
                                        pointSelect=np.array([[mousePoint.x(),self.y[index+1]]]) # tableau de 1 point = le point courant
                                        #pointSelect=np.array([[mousePoint.x(),mousePoint.y()]]) # tableau de 1 point = le point courant
                                        self.courbe2.setData(pointSelect[:,0],pointSelect[:,1]) # met à jour le point courant
                                        #self.label.setText("<span style='font-size: 10pt'>x=%0.1f,   <span style='color: red'>y1=%0.1f</span>" % (mousePoint.x(), self.y[index+1]))

                                        #self.labelX.setText("X = "+str(int(mousePoint.x())))
                                        #self.labelY.setText("Y = "+ str(self.y[index+1]))

                                                                        #-- met à jour leslcdNumber
                                        # actualise lcdNumber
                                        self.lcdNumberValeurBrute.display(self.y[index+1]) # affiche la valeur

                                        # équiv map : interp(256,[1,512],[5,10]) (package numpy)
                                        self.valeurCalc=np.interp(self.y[index+1], [-1,1],[float(self.lineEditValeurMin.text()),float(self.lineEditValeurMax.text())]) # équiv map
                                        self.lcdNumberValeurCalc.display(self.valeurCalc)


                                        #-- met à jour position des lignes --
                                        self.vLine.setPos(mousePoint.x())
                                        #self.hLine.setPos(mousePoint.y())
                                        self.hLine.setPos(self.y[index+1]) # point sur la courbe

        # --- 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 courbe s'affiche progressivement simulant un affichage "temps réel" :
    • le bouton de réglage permet de régler la vitesse
    • le bouton de stop permet de stopper
  • En mode stoppé, le point courant et une croix de ligne s'affiche sous le curseur de la souris.
  • Un premier clic permet alors de fixer le point, un second de réactiver le suivi du curseur