View  Edit  Attributes  History  Attach  Print  Search

PYQTLAB

PyQt Lab' : Graphiques Math : Pyqtgraph : Afficher le point courant sous la souris dans un graphique Pyqtgraph.

Par X. HINAULT - Juin 2013

Ce que l'on va faire ici

  • Afficher le point courant sous la souris dans un graphique Pyqtgraph.

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>495</width>
    <height>413</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>PyQt + pyqtgraph : Affichage courbe et point courant</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="QLabel" name="labelX">
   <property name="geometry">
    <rect>
     <x>5</x>
     <y>375</y>
     <width>71</width>
     <height>16</height>
    </rect>
   </property>
   <property name="text">
    <string>X=</string>
   </property>
  </widget>
  <widget class="QLabel" name="labelY">
   <property name="geometry">
    <rect>
     <x>80</x>
     <y>375</y>
     <width>231</width>
     <height>16</height>
    </rect>
   </property>
   <property name="text">
    <string>Y=</string>
   </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 '/home/xavier/www/mon_arduino/python_avec_arduino/mes_pyQt_tutos_v2/pyqtgraph/tuto_pyqt_pyqtgraph_courbe_ligne_souris/codes/tuto_pyqt_pyqtgraph_courbe_ligne_souris.ui'
#
# Created: Sat May 25 22:50:31 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(495, 413)
        self.graph = PlotWidget(Form)
        self.graph.setGeometry(QtCore.QRect(5, 10, 480, 360))
        self.graph.setObjectName(_fromUtf8("graph"))
        self.labelX = QtGui.QLabel(Form)
        self.labelX.setGeometry(QtCore.QRect(5, 375, 71, 16))
        self.labelX.setObjectName(_fromUtf8("labelX"))
        self.labelY = QtGui.QLabel(Form)
        self.labelY.setGeometry(QtCore.QRect(80, 375, 231, 16))
        self.labelY.setObjectName(_fromUtf8("labelY"))

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

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQt + pyqtgraph : Affichage courbe et point courant", None, QtGui.QApplication.UnicodeUTF8))
        self.labelX.setText(QtGui.QApplication.translate("Form", "X=", None, QtGui.QApplication.UnicodeUTF8))
        self.labelY.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 - Tous droits réservés - GPLv3
# Mai 2013 - www.mon-club-elec.fr

# --- importation des modules utiles ---
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...
# pas indispensable sinon car pyqtgraph est inclut par la déclaration dans QtDesigner de PlotWidget

import numpy as np # math et tableaux

# --- importation du fichier de description GUI ---
from tuto_pyqt_pyqtgraph_courbe_point_souris import *

#-- variables globales
resolution=10.0 # intervalle entre 2 valeur x

# classe principale contenant le code actif
class myApp(QWidget, Ui_Form): # la classe reçoit le Qwidget principal ET la classe définie dans test.py obtenu avec pyuic4

        # Note : ici self représente la classe

        def __init__(self, parent=None):
                QWidget.__init__(self) # initialise le Qwidget principal
                self.setupUi(parent) # Obligatoire

                #-- variables utiles --

                #-- connexion des signaux des widgets --

                # ------ 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

                # adaptation échelle axes
                self.graph.enableAutoRange(axis=pg.ViewBox.YAxis, enable=False) # fonction plotItem : désactive autoscale Y
                self.graph.setYRange(-9,9) # 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

                #-- initialise données --
                self.x = np.arange(0.0, 361.0, 1.0/resolution) # 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=sin(x)
                self.y=10*np.sin(np.radians(self.x))# crée un tableau de valeur y basé sur x
                print(self.y) # debug - affiche les valeurs y

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

                # -- ajout d'une légende
                #self.label = pg.LabelItem()
                #self.graph.addItem(self.label)

                #-- 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]

                # objet viewbox
                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

        # 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
                                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 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 le point courant
                print ("Mouse clicked")

                # ici gestion du clic souris
                print evt

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

        #--- autres fonctions actives
        # ...


# fonction principale exécutant l'application Qt                       
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

# pour rendre le fichier *.py exécutable
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
  • Déplacer le curseur de la souris sur le graphique : le point courant à l'abscisse du curseur doit s'afficher.
  • La molette de souris assure le zoom