QT ‘n Plot – Simple Ones

Los gráficos pueden hacer divertidas cosas que no lo son, pueden convertir números fríos que no dicen mucho en cosas que cobran sentido, pueden poner una sonrisa en tu cara y hacerte ver el mundo color de rosa. Bah, no tanto, pero están buenos. Un ejemplo es éste, cortesía de xkcd

fucking_blue_shells

Matplotlib es una librería (vulg. biblioteca) de Python basada en MATLAB, que nos permite hacer super-gráficos de muchas formas y colores, y si andamos con ganas,podemos integrarlos a nuestros programas de todos los días.

Lo que voy a hacer ahora es mostrar cómo trazar un par de gráficos (y cuando digo un par me refiero a 2) e incrustarlos en una aplicación que tiene una GUI basada en PyQt4 (léase Pie Cute). Los gráficos están inspirados en el clásico chiste de Ramón, que tenía un camión y se iba para Gijón. No cuento el chiste acá por respeto al señor Ramón.

 

 

 
Cuando terminemos, la cosa va a quedar algo así:

 
CamionDeRamon

Los requisitos mínimos para poder hacer esto son:

  • Python (yo uso la versión 2.6)
  • Numpy (tiene clases y funciones para trabajar con arreglos)
  • Matplotlib: hay un instalador para windows y una versión independiente de la plataforma (en este último caso va a haber que usar distutils, como lo indiqué en mi post anterior)
  • PyQt
  • (opcional) Un buen entorno de desarrollo, como Eclipse+PyDev, o Wing IDE Professional si andan platudos

Empecemos por el principio:

  1. Importamos las cosas que vamos a usar:
    import sys
    
    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    import matplotlib
    import matplotlib.pyplot as plot
    
    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.figure import Figure
    import numpy as np
    
  2. Creamos e inicializamos la ventana en la que va a transcurrir la acción:
    class FormMain(QMainWindow):
        def __init__(self, parent=None):
            super(FormMain, self).__init__(parent)
    
            self.cc = plot.matplotlib.colors.ColorConverter()
            self.__initialize__()
    
  3. Escribimos el código de inicialización de la ventana:
    def __initialize__(self):
            self.setWindowTitle(QString(u"El camión de Ramón"))
    
            # mainWidget
            mainWidget = QWidget(self)
            self.setCentralWidget(mainWidget)
    
            # layoutMain
            self.layoutMain = QVBoxLayout()
            mainWidget.setLayout(self.layoutMain)
    
            # Figure and canvas configuration
            self.fig = Figure((6.0,6.0), dpi=100,\
    facecolor=self.cc.to_rgb("lightsteelblue"))
            self.fig.subplots_adjust(hspace=0.25)
            self.canvas = FigureCanvas(self.fig)
            self.canvas.setParent(mainWidget)
            self.layoutMain.addWidget(self.canvas)
            self.axes = self.fig.add_subplot(211)
            self.axes.get_xaxis().set_visible(False)
            self.axes2 = self.fig.add_subplot(212)
    
            #layoutSettings
            self.layoutSettings = QHBoxLayout()
            self.layoutMain.addLayout(self.layoutSettings)
    
            #lblInitialSpeed
            self.lblInitialSpeed = QLabel("Velocidad inicial:")
            self.layoutSettings.addWidget(self.lblInitialSpeed)
    
            #spnInitialSpeed
            self.spnInitialSpeed = QSpinBox()
            self.spnInitialSpeed.setValue(6)
            self.spnInitialSpeed.setMaximum(20)
            self.layoutSettings.addWidget(self.spnInitialSpeed)
            self.connect(self.spnInitialSpeed, SIGNAL("valueChanged(int)"),\
    self.UpdatePlots)
    
            self.layoutSettings.addSpacing(12)
    
            #lblAcceleration
            self.lblAcceleration = QLabel(u"Aceleración:")
            self.layoutSettings.addWidget(self.lblAcceleration)
    
            #spnAcceleration
            self.spnAcceleration = QSpinBox()
            self.spnAcceleration.setValue(7)
            self.spnAcceleration.setMaximum(10)
            self.layoutSettings.addWidget(self.spnAcceleration)
            self.connect(self.spnAcceleration, SIGNAL("valueChanged(int)"),\
    self.UpdatePlots)
    
            self.layoutSettings.addSpacing(12)
    
            #lblTime
            self.lblTime = QLabel("Tiempo:")
            self.layoutSettings.addWidget(self.lblTime)
    
            #spnTime
            self.spnTime = QSpinBox()
            self.spnTime.setValue(20)
            self.spnTime.setMinimum(1)
            self.spnTime.setMaximum(20)
            self.layoutSettings.addWidget(self.spnTime)
            self.connect(self.spnTime, SIGNAL("valueChanged(int)"),\
    self.UpdatePlots)
    
            self.layoutSettings.addStretch()
    
            self.UpdatePlots()
    

    Ahora la explicación por partes:

    self.fig = Figure((6.0,6.0), dpi=100, facecolor=self.cc.to_rgb("lightsteelblue"))

    Definimos una figura de 6 pulgadas por 6 pulgadas, con una resolución de 100dpi y un fondo de color lightsteelblue.

    self.fig.subplots_adjust(hspace=0.25)
    

    Establecemos una distancia entre los dos subgráficos igual a 1/4 del tamaño de ellos.

    self.canvas = FigureCanvas(self.fig)
            self.canvas.setParent(mainWidget)
            self.layoutMain.addWidget(self.canvas)
    

    Creamos un Canvas que es el mismísimo QWidget que se va a mostrar en la ventana, y lo agregamos al layout principal.

    self.axes = self.fig.add_subplot(211)
            self.axes.get_xaxis().set_visible(False)
            self.axes2 = self.fig.add_subplot(212)
    

    Creamos los dos subgráficos. De los números que se le pasan a add_subplot, el primer dígito indica el número de filas que va a haber, y el segundo indica el número de columnas (estos dos dígitos tienen que ser iguales en ambos subgráficos, o van a pasar cosas muy desagradables, que ni el más macho de los machos soportaría ver). El tercer dígito indica en qué orden va a aparecer el subgráfico.

    Lo que viene después es código de creación de componentes visuales (Spinboxes y Labels) que se explica solo, lo único que hace falta aclarar es que conectamos las Spinboxes al método UpdatePlots, que vamos a definir a continuación, para que se actualicen los gráficos cada vez que se modifican los valores de velocidad, aceleración y tiempo. Finalmente llamamos a ese método para mostrar los gráficos por primera vez.

  4. Actualizamos los gráficos de velocidad y de posición, y reflejamos los cambios en el Canvas:

    def UpdatePlots(self):
            # Update the subplots
            self.UpdateSpeedPlot()
            self.UpdatePositionPlot()
            self.canvas.draw()
    
  5. Definimos dos pequeños métodos para obtener la velocidad y la posición dados los parámetros especificados por el usuario. Es importante aclarar que como el parámetro time va a ser un arreglo, estos métodos van a devolver arreglos.

    def Speed(self, initialSpeed, acceleration, time):
            return initialSpeed + acceleration * time
        
        def Position(self, initialPosition, initialSpeed, acceleration, time):
            return initialPosition + initialSpeed * time + 0.5 * acceleration \
    * (time ** 2)
    
  6. Cada uno de estos métodos actualiza uno de los gráficos, no explico nada porque ya está todo dicho en los comentarios :) (notarán que antes estaban en inglés y cambiaron súbitamente de idioma, pero es que lo hice para ahorrarme la explicación aparte)

    def UpdateSpeedPlot(self):
            v0 = self.spnInitialSpeed.value()
            a = self.spnAcceleration.value()
            t = self.spnTime.value()
            
            # x es un arreglo con cada segundo, desde 0 a t.
            x = np.arange(0, t + 1)
            # y es un arreglo con la velocidad en cada segundo.
            y = self.Speed(v0, a, x)    
            
            # Limpiamos el gráfico y asignamos lo que va a decir cada eje.
            self.axes.clear()
            self.axes.set_ylabel("Velocidad (m/s)")
            self.axes.set_xlabel("Tiempo (s)")
            # Hacemos que se muestre la cuadrícula.
            self.axes.grid(True)
            
            # Creamos una línea que utilice el arreglo x en el eje x e y en el eje y.
            line = plot.Line2D(x, y, linewidth=2,\
    color=self.cc.to_rgb("yellowgreen"))
            # Agregamos la línea al gráfico y la trazamos.  
            self.axes.add_line(line)
            self.axes.plot()
            
            # Las tres últimas instrucciones son equivalentes en este caso a
            # self.axes.plot(x, y, linewidth=2,\
    color=self.cc.to_rgb("yellowgreen"))
            # pero es más prolijo trabajar cada línea por separado, especialmente cuando
            # trabajamos con varias.
            
            # Fijamos los límites de los ejes coordenados.
            self.axes.set_xlim(0, t)
            self.axes.set_ylim(0, 220)
            
        def UpdatePositionPlot(self):
            v0 = self.spnInitialSpeed.value()
            a = self.spnAcceleration.value()
            t = self.spnTime.value()
            
            # x es un arreglo con cada segundo, desde 0 a t.
            x = np.arange(0, t + 1)
            # y es un arreglo con la posición en cada segundo.
            y = self.Position(0, v0, a, x)
            
            # Limpiamos el gráfico y asignamos lo que va a decir cada eje.
            self.axes2.clear()
            self.axes2.set_ylabel(u"Posición (m)")
            self.axes2.set_xlabel("Tiempo (s)")
            # Hacemos que se muestre la cuadrícula.
            self.axes2.grid(True)
            
            # Creamos una línea que utilice el arreglo x en el eje x e y en el eje y.
            line = plot.Line2D(x, y, linewidth=2,\
    color=self.cc.to_rgb("yellowgreen"))
            # Agregamos la línea al gráfico y la trazamos.   
            self.axes2.add_line(line)
            self.axes2.plot()
            
            # Fijamos los límites de los ejes coordenados.
            self.axes2.set_xlim(0, t)
            self.axes2.set_ylim(0, 2500)       
    

Bueno, ya estamos, acá dejo el código completo, diviértanse haciendo gráficos y no se lastimen :)

#coding=utf-8

import sys

from PyQt4.QtCore import *
from PyQt4.QtGui import *

import matplotlib
import matplotlib.pyplot as plot

from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np


class FormMain(QMainWindow):
    def __init__(self, parent=None):
        super(FormMain, self).__init__(parent)
        self.cc = plot.matplotlib.colors.ColorConverter()
        self.__initialize__()
        
        
    def __initialize__(self):
        self.setWindowTitle(QString(u"El camión de Ramón"))
        
        # mainWidget
        mainWidget = QWidget(self)
        self.setCentralWidget(mainWidget)
        
        # layoutMain
        self.layoutMain = QVBoxLayout()
        mainWidget.setLayout(self.layoutMain)
        
        # Figure and canvas configuration 
        self.fig = Figure((6.0,6.0), dpi=100, facecolor=self.cc.to_rgb("lightsteelblue"))
        self.fig.subplots_adjust(hspace=0.25)
        self.canvas = FigureCanvas(self.fig)
        self.canvas.setParent(mainWidget)
        self.layoutMain.addWidget(self.canvas)
        self.axes = self.fig.add_subplot(211)
        self.axes.get_xaxis().set_visible(False)
        self.axes2 = self.fig.add_subplot(212)
        
        #layoutSettings
        self.layoutSettings = QHBoxLayout()
        self.layoutMain.addLayout(self.layoutSettings)
        
        #lblInitialSpeed
        self.lblInitialSpeed = QLabel("Velocidad inicial:")
        self.layoutSettings.addWidget(self.lblInitialSpeed)
        
        #spnInitialSpeed
        self.spnInitialSpeed = QSpinBox()
        self.spnInitialSpeed.setValue(6)
        self.spnInitialSpeed.setMaximum(20)
        self.layoutSettings.addWidget(self.spnInitialSpeed)
        self.connect(self.spnInitialSpeed, SIGNAL("valueChanged(int)"), self.UpdatePlots)
        
        self.layoutSettings.addSpacing(12)
        
        #lblAcceleration
        self.lblAcceleration = QLabel(u"Aceleración:")
        self.layoutSettings.addWidget(self.lblAcceleration)
        
        #spnAcceleration
        self.spnAcceleration = QSpinBox()
        self.spnAcceleration.setValue(7)
        self.spnAcceleration.setMaximum(10)
        self.layoutSettings.addWidget(self.spnAcceleration)
        self.connect(self.spnAcceleration, SIGNAL("valueChanged(int)"), self.UpdatePlots)
        
        self.layoutSettings.addSpacing(12)

        #lblTime
        self.lblTime = QLabel("Tiempo:")
        self.layoutSettings.addWidget(self.lblTime)
        
        #spnTime
        self.spnTime = QSpinBox()
        self.spnTime.setValue(20)
        self.spnTime.setMinimum(1)
        self.spnTime.setMaximum(20)
        self.layoutSettings.addWidget(self.spnTime)
        self.connect(self.spnTime, SIGNAL("valueChanged(int)"), self.UpdatePlots)
        
        self.layoutSettings.addStretch()
        
        
        self.UpdatePlots()
        
    def Speed(self, initialSpeed, acceleration, time):
        return initialSpeed + acceleration * time
    
    def Position(self, initialPosition, initialSpeed, acceleration, time):
        return initialPosition + initialSpeed * time + 0.5 * acceleration * (time ** 2)
    
    def UpdatePlots(self):
        # Update the subplots
        self.UpdateSpeedPlot()
        self.UpdatePositionPlot()
        self.canvas.draw()

    def UpdateSpeedPlot(self):
        v0 = self.spnInitialSpeed.value()
        a = self.spnAcceleration.value()
        t = self.spnTime.value()
        
        # x es un arreglo con cada segundo, desde 0 a t.
        x = np.arange(0, t + 1)
        # y es un arreglo con la velocidad en cada segundo.
        y = self.Speed(v0, a, x)    
        
        # Limpiamos el gráfico y asignamos lo que va a decir cada eje.
        self.axes.clear()
        self.axes.set_ylabel("Velocidad (m/s)")
        self.axes.set_xlabel("Tiempo (s)")
        # Hacemos que se muestre la cuadrícula.
        self.axes.grid(True)
        
        # Creamos una línea que utilice el arreglo x en el eje x e y en el eje y.
        line = plot.Line2D(x, y, linewidth=2, color=self.cc.to_rgb("yellowgreen"))
        # Agregamos la línea al gráfico y la trazamos.  
        self.axes.add_line(line)
        self.axes.plot()
        
        # Las tres últimas instrucciones son equivalentes en este caso a
        # self.axes.plot(x, y, linewidth=2, color=self.cc.to_rgb("yellowgreen"))
        # pero es más prolijo trabajar cada línea por separado, especialmente cuando
        # trabajamos con varias.
        
        # Fijamos los límites de los ejes coordenados.
        self.axes.set_xlim(0, t)
        self.axes.set_ylim(0, 220)
        
    def UpdatePositionPlot(self):
        v0 = self.spnInitialSpeed.value()
        a = self.spnAcceleration.value()
        t = self.spnTime.value()
        
        # x es un arreglo con cada segundo, desde 0 a t.
        x = np.arange(0, t + 1)
        # y es un arreglo con la posición en cada segundo.
        y = self.Position(0, v0, a, x)
        
        # Limpiamos el gráfico y asignamos lo que va a decir cada eje.
        self.axes2.clear()
        self.axes2.set_ylabel(u"Posición (m)")
        self.axes2.set_xlabel("Tiempo (s)")
        # Hacemos que se muestre la cuadrícula.
        self.axes2.grid(True)
        
        # Creamos una línea que utilice el arreglo x en el eje x e y en el eje y.
        line = plot.Line2D(x, y, linewidth=2, color=self.cc.to_rgb("yellowgreen"))
        # Agregamos la línea al gráfico y la trazamos.   
        self.axes2.add_line(line)
        self.axes2.plot()
        
        # Fijamos los límites de los ejes coordenados.
        self.axes2.set_xlim(0, t)
        self.axes2.set_ylim(0, 2500)
       

app = QApplication(sys.argv)
frmMain = FormMain()
frmMain.show()
app.exec_()
Anuncios

4 responses to this post.

  1. […] que suelo hacer, como subir las letras de alguna canción que absolutamente nadie conoce, mostrar cómo hacer algunos gráficos extraños, resumir la epistemología, hacer la review de algún CD o alguna película, o explicar cómo puede […]

    Responder

  2. Posted by FranK on julio 14, 2011 at 6:37 am

    Hola, estoy haciendo un proyecto para la Universidad Politécnica de Valencia y me gustaría añadir gráficas (representación temporal de los datos) a la GUI, estoy usando Python y QT, asi que matplotlib es una buena opción, la pregunta es:

    Como incluir las gráficas en mi mainwindow?

    Mi correo: frankynama@gmail.com

    Responder

    • Posted by Rêveur on julio 14, 2011 at 10:39 am

      Hola Frank, el vínculo entre PyQT y Matplotlib es la clase FigureCanvasQTAgg, que se importa en la línea 11 (del código completo).

      En la línea 35 se crea una instancia de esta clase (FigureCanvas es el alias que definí en la línea 11 para FigureCanvasQTAgg), pasándole la figura de Matplotlib que contiene los gráficos que se quieren mostrar, y después se agrega al layoutMain:

      self.canvas = FigureCanvas(self.fig)
      self.canvas.setParent(mainWidget)
      self.layoutMain.addWidget(self.canvas)

      mainWidget es solamente un widget genérico que se establece como widget central de la ventana principal, y al que se le asocia un layout.

      Espero que te haya servido.

      Responder

  3. Posted by FranK on julio 14, 2011 at 1:25 pm

    Gracias!

    Por ahora ya le e puesto el recuadro azul, ahora solo faltan las gráficas!

    Ya te contare que tal me va!

    Responder

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: