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

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í:

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:
- 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
- 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__()
- 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, elprimer 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.
-
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()
-
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)
-
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értnse 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_()