SymPy, calculando la ecuación de un plano dados tres puntos

SymPy es una de esas librerías que lo mismo sirven para hacer grandes cosas, que para desempolvar recuerdos y aplicarlos en cuestiones más orientadas a la etapa académica. Y es que las cuestiones de algebra simbólica suelen ser muy divertidas y lo suficientemente interesantes para mantener a un individuo ocupado.

Bueno, sin más preámbulos, en este post vamos a ver cómo utilizar SymPy para calcular la ecuación de un plano dados tres puntos contenidos en este.

Primero un poco de geometría elemental. Sean los tres puntos contenidos en el plano los siguientes:

P1 = (x1, y1, z1)

P2 = (x2, y2, z2)

P3 = (x3, y3, z3)

Luego, la ecuación implícita del plano podemos obtenerla resolviendo la ecuación dada por el determinante siguiente:

$$ \left|\begin{matrix} x - x_1 & y - y_1 & z - z_1 \\ x_2 - x_1 & y_2 - y_2 & z_2 - z_1 \\ x_3 - x_1 & y_3 - y_1 & z_3 - z_1 \\ \end{matrix}\right| = 0 $$

La solución tradicional creo que, llegados a este punto, todos podemos obtenerla sin ningún tipo de sobresalto. Ahora, la idea es implementar una solución utilizando SymPy.

SymPy dispone de una clase Matrix, que recibe como argumentos de entrada una lista de valores numéricos o bien de cualquier variable simbólica que haya sido definida previamente. Está claro que en este caso los valores de las coordenadas de los puntos son conocidos, pero las variables \(x, y, z\) serán variables de tipo simbólico. Para calcular el determinante de una matriz podemos utilizar la función det, que recibe como argumento de entrada un objeto de la clase Matrix. Una vez resuelto el determinante tendremos una ecuación de la forma:

Ax + By + Cz − k = 0

Veamos el código implementado en SymPy:

from sympy import Matrix, det
from sympy.abc import x,y,z

P1 = (1,2,3)
P2 = (0,-1,1)
P3 = (-2,1,-2)

M = Matrix([[x-P1[0] , y-P1[1] , z-P1[2]] ,
[P2[0]-P1[0] , P2[1]-P1[1] , P2[2]-P1[2]],
[P3[0]-P1[0] , P3[1]-P1[1] , P3[2]-P1[2]]])

print(u"Ecuación implícita: %s = 0"%det(M))

Lo cual nos devolverá en consola la ecuación implícita del plano:

Ecuación implícita: 13*x + y - 8*z + 9 = 0

Ahora bien, si requerimos la ecuación anterior expresada de forma explícita como una función bivariable del tipo \(z=f(x,y)\), entonces, debemos utilizar la función solve y resolver la ecuación planteada respecto a \(z\), por ejemplo:

from sympy import Matrix, solve, det
from sympy.abc import x,y,z

P1 = (1,2,3)
P2 = (0,-1,1)
P3 = (-2,1,-2)

M = Matrix([[x-P1[0] , y-P1[1] , z-P1[2]] ,
[P2[0]-P1[0] , P2[1]-P1[1] , P2[2]-P1[2]],
[P3[0]-P1[0] , P3[1]-P1[1] , P3[2]-P1[2]]])

sol = solve(det(M), z)
print(u"Ecuación implícita: %s = 0"%det(M))
print(u"Ecuación explícita: z=%s"%(sol[0]))

Resultando:
Ecuación implícita: 13*x + y - 8*z + 9 = 0
Ecuación explícita: z=13*x/8 + y/8 + 9/8

Incluso podemos graficar nuestro plano utilizando la función plot3d del módulo plotting:

from sympy import latex
from sympy.plotting import plot3d

plot3d(sol[0], (x,0,5), (y,0,5), title="$z = %s$"%(latex(sol[0])))


Funciones definidas a trozos (piecewise) con NumPy

Una función definida a trozos es una función real \(f\) de una variable real \(x\), cuya definición está dada por varios conjuntos disjuntos de su dominio. [1]

El ejemplo clásico de una función definida por secciones es la función valor absoluto \(abs(x)\), habitualmente definida por:

$$ f(x)= |x| = \left\{ \begin{matrix} -x & si \,\, x < 0 \\ x & si \,\, x \geq 0 \\ \end{matrix}\right. $$

De manera ordinaria, en NumPy, para definir una función en un intervalo tendríamos que crear un vector de \(n\) cantidad de puntos en ese intervalo, y posteriormente crear la expresión que define la función, por ejemplo, definiendo a \(f(x)=x\,\,cos(x)\)

>>> import numpy as np
>>> x=np.linspace(0,10)
>>> y=x*np.cos(x)

Ahora, dada la naturaleza de las funciones por tramos, estás no pueden definirse como en las líneas anteriores, puesto que la expresión que las define depende del intervalo. Una opción para crear una función a trozos sería definiendo un intervalo para cada expresión y posteriormente concatenar todo en un mismo arreglo, tanto para los intervalos como las expresiones.

Por ejemplo, definiendo la función valor absoluto en el intervalo \((-10,10)\):

import numpy as np

x1 = np.linspace(-10,0)
x2 = np.linspace(0,10)
y1 = -x1
y2 = x2
x = np.concatenate((x1,x2))
y = np.concatenate((y1,y2))

Si, lo anterior puede parecer un poco tedioso, así que NumPy también dispone de una función que nos ahorra el estar escribiendo mucho código: piecewise, la cual nos permite crear un arreglo a partir de otro (intervalo), seccionando este acorde a las expresiones/funciones pasadas como argumentos y a las condiciones lógicas para definir los subintervalos. En términos simples la sintaxis de piecewise es:

np.piecewise(x, logls, funls)

Donde x es un arreglo que define la variable independiente, logls una lista de condiciones lógicas para seccionar y definir los subintervalos, y funls una lista de funciones o constantes que definen el valor de la función para el subintervalo correspondiente.

Siguiendo con nuestro ejemplo de la función valor absoluto, haríamos algo como lo siguiente:

import numpy as np

x = np.linspace(-5, 5)
y = np.piecewise(x, [x<0, x>=0], [lambda x: -x, lambda x: x])

Desde luego también se pueden usar funciones ordinarias en lugar de lambdas.

Para hacer esto un poco más ilustrativo vamos a utilizar Matplotlib para trazar la gráfica correspondiente:

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-5, 5)
y = np.piecewise(x, [x<0, x>=0], [lambda x: -x, lambda x: x])

plt.plot(x, y)

plt.show()





Referencias:
[1]. https://es.wikipedia.org/wiki/Funci%C3%B3n_definida_a_trozos

Utilizando estilos en Matplotlib

Por defecto Matplotlib tiene un estilo definido, un aspecto muy característico y facilmente reconocible. Pero también permite personalizar los estilos de gráficas de una forma muy sencilla, utilizando hojas de estilos predefinidas y que vienen incluidas con Matplotlib, aunque también existe la posibilidad de crearlas, pero de eso hablaremos luego.

Para ver los estilos que tenemos disponibles podemos importar pyplot y posteriormente teclear lo siguiente:

>>> import matplotlib.pyplot as plt
>>> print plt.style.available
[u'labdls-dark',u'grayscale', u'bmh', u'dark_background', u'ggplot', u'fivethirtyeight']

Como puede observar lo anterior nos devuelve una lista con los estilos disponibles. El primer elemento de la lista es un estilo personalizada, así que seguro no estará en los estilos que le devuelva su consola.

Para utilizar un determinado estilo debe anteponer al código Matplotlib (justo después de las líneas de importación de módulos) lo siguiente:

plt.style.use(estilo)

Donde estilo es un string con el nombre del estilo a utilizar, por ejemplo:

import numpy as np
import matplotlib.pyplot as plt
plt.style.use('ggplot')

x = np.linspace(0, 4*np.pi, 100)
y1 = x*np.cos(x)
y2 = x*np.sin(x)
y3 = np.sin(x)+np.cos(x)
fig = plt.figure()
ax = fig.add_subplot(111)

ax.plot(x, y1, label="$x\,cos(x)$")
ax.plot(x, y2, label="$x\,sin(x)$")
ax.plot(x, y3, label="$sin(x)+cos(x)$")
ax.legend()
ax.set_xlabel("Tiempo (s)")
ax.set_ylabel("Amplitud (mm)")

plt.show()



Ahora nuestra gráfica luce un tanto distinta a lo que normalmente estamos acostumbrados en Matplotlib. Para dar un vistazo general a cómo se ven los demás estilos puede implementar el siguiente script:

import numpy as np
import matplotlib.pyplot as plt

X = np.random.random((10,3))

fig = plt.figure(figsize=(12,8))
styles = plt.style.available

for k,style in enumerate(styles):
plt.style.use(style)
cax = fig.add_subplot(2,3,k)
cax.set_title(style)
cax.plot(X)

plt.show()



Muy interesantes, pero claro, siempre hará falta un poco de personalización que añada ese toque final. Por ello en entradas posteriores hablaremos de cómo crear una hoja de estilos Matplotlib.

Graficar en Python con Matplotlib y NumPy

Trazar gráficas en Python es muy sencillo, para ello necesita tener instaladas las librerías matplotlib y numpy, las cuales pueden encontrarse y descargar de la red sin mayores obstáculos. Debe asegurarse que la versión de las librerías sea compatible con la versión de Python.

Si alguna vez ha utilizado MATLAB para estos mismos fines, las instrucciones le parecerán demasiado familiar.

Enseguida os adjunto un código sencillo y el resultado que produce:

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(0,10,0.1)
y = x*np.cos(x)

plt.plot(x,y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Lab DLS')
plt.show()



Las primeras dos líneas sirven para importar las librerías que se utilizarán. Recuerde que en Python pueden utilizarse seudónimos al cargar una librería (en este caso plt para matplotlib.pyplot y np para numpy).

En la línea se 4 se define el vector de la variable independiente utilizando la instrucción arange de la librería numpy, el cual crea un vector especificando el valor inicial, el valor final y el incremento como argumentos. La linea 5 crea simplemente un vector dependiente del primero.

La instrucción plot gráfica los vectores creados con anterioridad, teniendo como primer argumento el vector de la variable independiente. Con xlabel, ylabel y title se muestran las etiquetas correspondientes a los ejes horizontal, vertical y el título en la parte superior respectivamente. Finalmente, la instrucción show() sirve para mostrar la ventana gráfica creada y poder visualizar lo que se ha trazado.


Modificando la presentación...

Grosor de línea

Para modificar el grosor de línea basta con incluir como argumento adicional en plot la propiedad linewidth, tal como se muestra enseguida:

plt.plot(x,y,linewidth=4)

Color de línea

El siguiente ejemplo configura el color de la línea, en este caso rojo.

plt.plot(x,y,color='r')

También puede especificar el color utilizando una tupla de 3 elementos (R,G,B) en el intervalo [0 1], por ejemplo:

plt.plot(x,y,color=(0.8,0.9,0))

Agregando rejilla

Para agregar una rejilla debe incluirse la instrucción grid() como se muestra:

plt.grid()


Mostrar más de una gráfica

Para mostrar más de una gráfica en la misma ventana, utilice hold(True) después de haber creado la primer gráfica o bien antes de ella, tal como se muestra en el código siguiente:

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(0,10,0.2)
y1 = np.cos(x)
y2 = np.sin(x)

plt.plot(x,y1,'o',linewidth=3,color=(0.2,0.1,0.4))
plt.hold(True)
plt.plot(x,y2,'-',linewidth=2,color='g')
plt.grid()
plt.axis('equal')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Lab DLS')
plt.show()




Gráficas de barras en Matplotlib

Esta entrada tiene como objetivo mostrar el uso de Matplotlib/Python como herramienta para generar una gráfica de barras.

Bien, para nuestro ejemplo vamos a suponer que se tienen como datos las calificaciones de 5 alumnos guardadas en una lista, y que tenemos también otra lista con los nombres correspondientes a cada uno, evidentemente en el mismo orden. Os adjunto el script y enseguida la explicación de cada linea:


#-*- coding: utf-8 -*-
import matplotlib.pyplot as plt

fig = plt.figure(u'Gráfica de barras') # Figure
ax = fig.add_subplot(111) # Axes

nombres = ['Juan','Ana','Pablo','Ximena','Jorge']
datos = [90,88,78,94,93]
xx = range(len(datos))

ax.bar(xx, datos, width=0.8, align='center')
ax.set_xticks(xx)
ax.set_xticklabels(nombres)

plt.show()


La primera linea sirve para especificar la codificación utilizada en el fichero y con ello evitar todo tipo de "sorpresas" debido a la peculiaridad de nuestro idioma, de modo que es necesaria. La segunda linea importa el módulo pyplot de la librería Matplotlib utilizando el alias plt.

Luego, creamos una nueva ventana mediante el uso de figure, y añadimos enseguida un axes mediante la instrucción add_subplot(111), donde el 111 indica que solamente se tendrá un eje coordenado dentro de la ventana.

Posteriormente se definen las listas que servirán como entrada para trazar la gráfica de barras. En la lista nombre se guarda un arreglo de strings con los nombres de cada alumno, en datos se guardan las calificaciones correspondientes y xx es una lista de enteros desde 0 a N-1, donde N es el número de elementos que contiene la lista datos, xx servirá para especificar los puntos ubicados en el eje horizontal en los cuales se trazarán las barras.

Una vez se han creado las listas, se procede a trazar la gráfica de barras mediante la función bar, cuyos parámetros de entrada son la lista xx y la lista de datos y/o calificaciones. Además de lo anterior, pueden utilizarse keywords arguments como width o align que sirven para especificar el ancho y la alineación de las barras trazadas. Luego, la función set_xticks define las marcas utilizadas para etiquetar al eje horizontal, y set_xticklabels permite colocar una lista de strings como etiquetas personalizadas, que en este caso corresponde a la lista de nombres.

Finalmente se utiliza la función show para mostrar todo lo que se ha trazado. Se adjunta la salida gráfica del script en cuestión.


Sympy live, una "consola" de Python online.

Sympy live (http://live.sympy.org/) es una aplicación web que permite ejecutar código Python en línea, y claro, con la ventaja que permite utilizar la librería Sympy para realizar cálculos de computación simbólica.



A continuación se muestran algunas operaciones realizadas utilizando esta aplicación.

Manipulación algebraica

Expandiendo la expresión $(x+1) ^2$ y factorizando $x^2+6x-16$:


Resolviendo ecuaciones e inecuaciones



Sistemas de ecuaciones lineales



Derivadas



Integrales



Integrales múltiples




Ecuaciones diferenciales



Borrar líneas en Matplotlib de manera interactiva

Matplotlib es una librería normalmente utilizada para trazar gráficas que habrán de exportarse como un archivo "estático" e incluirse en algún tipo de documento posteriormente. Pero además, Matplotlib también tiene algunas características que permiten que el usuario pueda interactuar, tales como los widgets o los eventos definidos por el usuario. Y esta última característica vamos a aprovechar en este post para ver cómo poder borrar líneas de una gráfica Matplotlib una vez que esta ha sido creada, esto mediante la selección a través del mouse.

Los eventos en Matplotlib se "conectan" utilizando el método mpl_connect de la clase FigureCanvas, mediante la sintaxis siguiente:

hevt = fig.canvas.mpl_connect('tipo_evento', fun)

Donde hevt es una variable en la cual se guarda la referencia al evento y que puede ser utilizada para desconectarlo cuando no lo necesitemos más, fig es una instancia de la clase Figuretipo_evento es uno de los eventos que pueden ser conectados en Matplotlib, cuya lista puede ver aquí, y fun es una función en la cual deberá programarse la respuesta de nuestro programa cuando se lance el evento.

Por ahora nos interesa el tipo de evento pick_event, el cual se "lanza" cuando un objeto en el canvas actual es seleccionado. Así, para conectar nuestro evento haremos algo como lo siguiente:

pick = fig.canvas.mpl_connect("pick_event", OnSelect)

Ahora vamos a por todo el código y enseguida explicamos para qué cada cosa:

# -*- coding: utf-8 -*-
import wx
import matplotlib.pyplot as plt
import numpy as np

def OnSelect(event):
app = wx.App()
dlg = wx.MessageDialog(None, "Desea borrar",
'Matplotlib Demo', wx.YES_NO|wx.ICON_QUESTION)
if dlg.ShowModal() == wx.ID_YES:
event.artist.remove()
dlg.Destroy()
app.MainLoop()
fig.canvas.draw()

# Definir datos a plotear
X = np.random.random((10,5))
# Crear figure y axes
fig = plt.figure()
ax = fig.add_subplot(111)
# Graficar datos
ax.plot(X, picker=True)
# Conectar evento "pick_event"
pick = fig.canvas.mpl_connect("pick_event", OnSelect)
plt.show()



Primero, importamos, claro, los módulos a utilizar. Lo de wxPython es opcional, sólo nos servirá para confirmar si realmente queremos borrar cierta línea y puede sustituirse con cualquier otra librería gráfica, Tkinter por ejemplo. Luego, definimos unos datos aleatorios, creamos nuestra Figure y nuestro Axes de la manera en que se debe, y posteriormente ploteamos los datos utilizando plot, pero adicionando elkeyword argument picker=True para decirle a Matplotlib que para nuestro objeto gráfico resultante requerimos que esté disponible para ser seleccionado mediante el mouse. Y finalmente conectamos el evento de tipo "pick_event" al canvas correspondiente, pasando a la función OnSelect como la encargada de dar una respuesta a ese evento.

La función OnSelect bien puede reducirse a dos líneas si es que no requerimos confirmación de borrado, algo como:

def OnSelect(event):
event.artist.remove()
fig.canvas.draw()

El resto de código es para crear un cuadro de diálogo en wxPython que nos pregunta si realmente queremos borrar la línea que hemos seleccionado.


Podríamos mejorar un poquito nuestro "demo" si por ejemplo cada vez que seleccionamos una línea esta sea modificada para distinguirse un poco más del resto, por ejemplo modificar su grosor, y en caso de no confirmar su borrado entonces regresar al aspecto original. Agregando algunas líneas nos queda un código más o menos como este:

# -*- coding: utf-8 -*-
import wx
import matplotlib.pyplot as plt
import numpy as np

def OnSelect(event):
klw = 2
event.artist.set_lw(event.artist.get_lw() + klw)
fig.canvas.draw()
app = wx.App()
dlg = wx.MessageDialog(None, "Desea borrar",
'Matplotlib Demo', wx.YES_NO|wx.ICON_QUESTION)
if dlg.ShowModal() == wx.ID_YES:
event.artist.remove()
else:
event.artist.set_lw(event.artist.get_lw() - klw)
dlg.Destroy()
app.MainLoop()
fig.canvas.draw()

# Definir datos a plotear
X = np.random.random((10,5))
# Crear figure y axes
fig = plt.figure()
ax = fig.add_subplot(111)
# Graficar datos
ax.plot(X, picker=True)
# Conectar evento "pick_event"
pick = fig.canvas.mpl_connect("pick_event", OnSelect)
plt.show()




Y bueno, con esto finalizamos esta pequeña introducción a las formas interactivas de Matplotlib, desde luego existen muchas posibilidades para implementar, de tal modo que nos quede algo más chulo.

Una introducción a SymPy

SymPy es una librería de Python desarrollada para resolver problemas de matemáticas simbólicas. Existen diversos software comerciales que realizan estas tareas: Maple, Mathematica, MATLAB, entre otros, pero requieren una licencia de uso que puede resultar poco accesible en algunos casos. En cambio, SymPy se distribuye bajo licencia BSD, que en resumen permite el uso libre de la misma.

Importando SymPy

Para importar SymPy y disponer de todos los módulos y funciones que le componen puede hacerse de diversas formas:
  1. Forma tradicional
>>> import sympy

Es la manera más habitual, se carga toda la librería y se accede a cada una de las funciones mediante la sintaxis:

>>> r=sympy.funcion(args)
  1. Importando funciones seleccionadas
>>> from sympy import Symbol,integrate,sin,cos

De este modo se importan solamente las funciones que vayan a utilizarse, es recomendable cuando se utilizará un número reducido de las mismas. Proporciona cierta ventaja dado que para acceder a una función no es necesario anteponer el nombre de la librería (sympy), aunque esto mismo represente una desventaja en aquellos casos en los que existen funciones de diferentes librerías con el mismo nombre.
  1. Utilizando un alias o seudónimo
>>> import sympy as sp

Funciona del mismo modo que para el primer caso, con la diferencia que el usuario puede asignarle un nombre más corto o bien más representativo para hacer las llamadas a funciones.
Para los ejemplos que se mostrarán en esta entrada se utilizará la segunda forma.

Declarando una variable simbólica


Para declarar una variable simbólica podemos utilizar la función Symbol, para ello primero importamos la función y posteriormente declaramos una variable simbólica "x":

>>> from sympy import Symbol
>>> x=Symbol('x')
>>> x
x
>>> x+2
x + 2

Como puede verse, una vez se ha declarado la variable simbólica podemos utilizarle para formar expresiones algebraicas de todo tipo. Existe una forma más "simple" de declarar una variable simbólica, para ello habrá de importarse del módulo "abc" la letra correspondiente, por ejemplo:

>>> from sympy.abc import x

O bien:

>>> from sympy.abc import x,y,z

Lo anterior en el caso de que se requieran múltiples variables simbólicas.

Manipulaciones algebraicas


Factorizar una expresión algebraica.

Para factorizar una expresión algebraica podemos utilizar la función factor, por ejemplo suponga que se quiere factorizar la expresión (x^2+2x+1):

>>> from sympy import factor,Symbol
>>> x=Symbol('x')
>>> factor(x**2+2*x+1)
(x + 1)**2


Expandir una expresión algebraica


Enseguida se muestra un ejemplo de cómo "expandir" o multiplicar dos expresiones algebraicas.

>>> from sympy import Symbol,expand
>>> x=Symbol('x')
>>> expand((x+2)*(x-3))
x**2 - x - 6

Gráficas de contorno en Matplotlib

Una manera de visualizar una función de dos variables es usar un campo escalar, en el que el escalar \(z = f(x,y)\) se asigna al punto \((x,y)\). Un campo escalar puede caracterizarse por sus curvas de nivel (o líneas de contorno) a lo largo de las cuales el valor de \(f(x,y)\) es constante.



El trazo de gráficas de lineas de contorno o curvas de nivel puede hacerse en Matplotlib utilizando la función contour, por ejemplo:

import matplotlib.pyplot as plt 
import numpy as np

fig = plt.figure()
ax = fig.add_subplot(111)

x = y = np.linspace(-5, 5, 100)
X,Y = np.meshgrid(x,y)
Z = (-4*X)/(X**2 + Y**2 + 1)

cs = ax.contour(X,Y,Z)
plt.show()




Podemos aumentar el número de niveles si agregamos un argumento de entrada a la función contour, por ejemplo:

cs = ax.contour(X, Y, Z, 20)

Con lo anterior tendríamos representados 20 niveles o 20 planos de valores constantes f(x, y)=C.




Se pueden agregar etiquetas a cada curva de nivel si utilizamos el método clabel, por ejemplo:

import matplotlib.pyplot as plt 
import numpy as np

fig = plt.figure()
ax = fig.add_subplot(111)

x = y = np.linspace(-5, 5, 100)
X,Y = np.meshgrid(x,y)
Z = (-4*X)/(X**2 + Y**2 + 1)

cs = ax.contour(X, Y, Z, 15)
ax.clabel(cs, fontsize=8)

plt.show()


Utilizando grillas en wxPython (wx.grid.Grid)

Las grillas o conjunto de celdas se utilizan para mostrar conjuntos de datos numéricos y/o cualquier otro tipo en una interfaz sencilla y muy similar a las tradicionales celdas de las hojas de cálculos.

En wxPython para crear una grilla debemos importar el módulo wx.grid y utilizar la clase Grid de dicho módulo. Para inicializar/instanciar un objeto de la clase Grid debemos pasarle como argumentos mínimos el objeto padre y un ID, y enseguida debemos definir el número de filas y columnas que compondrán la grilla utilizando el método CreateGrid, tal cómo se observa en el siguiente ejemplo:

# -*- coding: utf-8 -*-
import wx
import wx.grid as wxgrid

class TestFrame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent=parent,title=title,size=(250,250))

self.grid = wxgrid.Grid(self, -1)
self.grid.CreateGrid(10,2)

self.Centre(True)
self.Show()

if __name__=='__main__':
app = wx.App()
fr = TestFrame(None, "Test Grid")
app.MainLoop()

Insertando valores


Y ahora, ¿cómo insertamos valores en cada celda?. Para rellenar las celdas de una grilla utilizamos el método SetCellValue, pasando como argumentos la posición fila-columna de la celda y un string con el valor a insertar. Algo como:

self.grid.SetCellValue(fila,columna,valor)

Donde fila y columna son valores enteros que especifican la posición de la celda y valor una cadena de caracteres. Veamos el siguiente ejemplo:

# -*- coding: utf-8 -*-
import wx
import wx.grid as wxgrid

class TestFrame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent=parent,title=title,size=(250,250))

filas = 10
columnas = 2
self.grid = wxgrid.Grid(self, -1)
self.grid.CreateGrid(filas,columnas)

for i in range(filas):
for j in range(columnas):
valor = "(%s,%s)"%(i,j)
self.grid.SetCellValue(i,j,valor)

self.Centre(True)
self.Show()

if __name__=='__main__':
app = wx.App()
fr = TestFrame(None, "Test Grid")
app.MainLoop()

Modificando el color de celdas

Para modificar el color de una celda debemos utilizar el método SetCellBackgroundColour al cual se le pasan como argumentos la posición de la celda y el color a usar (en forma de cadena hexadecimal o bien mediante la clase wx.Colour). En el siguiente ejemplo se muestra como colorear las filas impares de un color determinado:

# -*- coding: utf-8 -*-
import wx
import wx.grid as wxgrid

class TestFrame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent=parent,title=title,size=(250,250))

filas = 10
columnas = 2
self.grid = wxgrid.Grid(self, -1)
self.grid.CreateGrid(filas,columnas)

# Asignando valores
for i in range(filas):
for j in range(columnas):
valor = "(%s,%s)"%(i,j)
self.grid.SetCellValue(i,j,valor)

# Modificando el color de fondo
for ii in range(0,filas,2):
for jj in range(columnas):
self.grid.SetCellBackgroundColour(ii,jj,"#fafa77")

self.Centre(True)
self.Show()

if __name__=='__main__':
app = wx.App()
fr = TestFrame(None, "Test Grid")
app.MainLoop()




Para quitar las etiquetas de filas, que a veces pueden resultar poco convenientes, puede utilizar el método SetRowLabelSize y pasar 0 (cero) como argumento.

self.grid.SetRowLabelSize(0)

Personalizando encabezados y anchos de columnas


Los encabezados (headers) de columnas por defecto están rotulados como en una hoja de cálculo (letras en orden alfabético), pero pueden personalizarse utilizando el método SetColLabelValue. Además también puede modificarse el ancho por defecto que tienen las columnas mediante el método SetColSize. En el siguiente ejemplo se muestra lo mencionado anteriormente:

# -*- coding: utf-8 -*-
import wx
import wx.grid as wxgrid
from random import randint

class TestFrame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent=parent,title=title,size=(250,250))

filas = 10
columnas = 2
self.grid = wxgrid.Grid(self, -1)
self.grid.CreateGrid(filas,columnas)
self.grid.SetRowLabelSize(0)

# Asignando valores aleatorios
for i in range(filas):
for j in range(columnas):
valor = "%g"%(randint(1,100))
self.grid.SetCellValue(i,j,valor)

# Modificando el color de fondo
for ii in range(0,filas,2):
for jj in range(columnas):
self.grid.SetCellBackgroundColour(ii,jj,"#fafa77")

# Modificando los encabezados y anchos de columna
encabezados = ["Tiempo (s)", "Amplitud (mm)"]
anchos = [100, 100]
for n,col in enumerate(range(columnas)):
self.grid.SetColLabelValue(col,encabezados[n])
self.grid.SetColSize(col,anchos[n])

self.Centre(True)
self.Show()

if __name__=='__main__':
app = wx.App()
fr = TestFrame(None, "Test Grid")
app.MainLoop()




Y bueno, hasta aquí esta entrada introductoria al uso de grillas en wxPython, ya estaremos viendo posteriormente características más avanzadas que permitan personalizar mejor una grilla.


Y una mini-aplicación


A continuación se adjunta una mini-aplicación en la cual se utiliza un grilla de 2x2 para emular los elementos de una matriz de 2x2, de la cual habrá de calcularse el determinante. Esta mini-aplicación incluye algunas consideraciones básicas sobre eventos en la grilla que posiblemente resulten de utilidad para el lector:

# -*- coding: utf-8 -*-
import wx
import wx.grid as wxgrid

class TestFrame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent=parent,title=title,size=(200,200))
self.panel = wx.Panel(self, -1)
self.sz = wx.BoxSizer(wx.VERTICAL)

# Grilla
filas = 2
columnas = 2
self.grid = wxgrid.Grid(self.panel, -1)
self.grid.CreateGrid(filas,columnas)
self.grid.SetRowLabelSize(60)
for col in range(columnas):
self.grid.SetColSize(col, 60)
self.grid.SetColLabelValue(col, "%s"%(col+1))

# Campo de texto para resultados
self.res = wx.TextCtrl(self.panel, -1, u"", style=wx.TE_CENTRE)

# Botón para calcular
self.bt = wx.Button(self.panel, -1, u"Calcular")

# Agregando elementos al sizer
self.sz.Add(self.grid, 5, wx.EXPAND|wx.ALL, 2)
self.sz.Add(self.res, 1, wx.EXPAND|wx.ALL, 2)
self.sz.Add(self.bt, 1, wx.ALIGN_CENTRE|wx.ALL, 2)

# Conectando eventos
self.Bind(wx.EVT_BUTTON, self.OnCalc, self.bt)
self.Bind(wxgrid.EVT_GRID_CELL_CHANGED, self.OnCellEdit)

self.panel.SetSizer(self.sz)
self.Centre(True)
self.Show()

def OnCalc(self,event):
"""
Ejecuta el cálculo del determinante, utilizando
como punto de entrada la matriz obtenida mediante
el método GetMatrix. El resultado obtenido se
muestra en el TextCtrl destinado para tal uso.
"""
M = self.GetMatrix()
if M is None:
wx.MessageBox("Rellene todas las celdas...")
return None
det = (M[0][0]*M[1][1]) - (M[0][1]*M[1][0])
res = "det(M) = %s"%(det)
self.res.SetValue(res)

def GetMatrix(self):
"""
Construye una lista de listas para
representar una matriz de 2x2.
"""
rows = self.grid.GetNumberRows()
cols = self.grid.GetNumberCols()
M = []
for row in range(rows):
_crow = []
for col in range(cols):
cval = self.grid.GetCellValue(row,col)
if not cval:
return None
_crow.append(float(cval))
M.append(_crow)
return M

def OnCellEdit(self,event):
"""
Verifica que el valor introducido en una
celda sea un número, de lo contrario elimina el valor.
"""
ii,jj = event.GetRow(), event.GetCol()
inval = self.grid.GetCellValue(ii,jj)
try:
cnum = float(inval)
except:
self.grid.SetCellValue(ii,jj,"")


if __name__=='__main__':
app = wx.App()
fr = TestFrame(None, "Determinante")
app.MainLoop()