Crea un graficador de funciones con Tkinter, Matplotlib y Numpy

En la presente ocasión me dispongo a crear un graficador de funciones (un programa consistente en una ventana que representará la gráfica correspondiente a la función que introduzcamos en una entrada que se mostrará debajo de la gráfica) haciendo uso de las librerías “tkinter”, “matplotlib” y “numpy”:

Como es natural, lo primero que haremos será crear la ventana que integre nuestra gráfica (que crearemos con «matplotlib») con los elementos que emplearemos para introducir la función a representan (y también el rango de «x») así como el botón «SET» que mostrará la representación (elementos, estos, creados con «tkinter»). No obstante, antes de iniciar esta labor, importaremos los recursos necesarios de las librerías «tkinter» (para los widgets), «matplotlib» (para mostrar la gráfica) y «numpy» (para efectuar los cálculos sobre las series de datos):

Ya que vamos a crear una ventana «tkinter» que contenga la gráfica, el siguiente paso será crear dicha ventana (a la que con «wm_title») le daremos el nombre de nuestra aplicación «Graficador». A su vez, también especificaremos las dimensiones de la misma (que serán adecuadas para una buena visualización gráfica):

Aunque podemos usar el que viene por defecto, también podemos especificar el estilo visual que va atener nuestra gráfica (para ello empleamos el la función «style.use()») a la que pasaremos el nombre del estilo deseado («fivethirtyeight» en nuestro caso). Para ver el listado de estilos disponibles usaremos el método «style.available» como se muestra a continuación:

Una vez escogido el estilo de la gráfica, procederemos a crear el objeto que la representará, el cual insertaremos en el área de dibujo que crearemos a continuación:

Para ver como esta quedando la aplicación, vamos a introducir la función para visualización de gráficas, «plt.show()»:

Si ahora ejecutamos lo hecho, obtendremos el siguiente resultado:

Como se ve, hasta ahora ya tenemos creada la ventana, con el área en el que se va a dibujar nuestra gráfica. Como dijimos antes, nuestro programa va a representar la gráfica correspondiente a la función que nosotros le proporcionemos (y si queremos, también dentro del rango que pidamos). Por ello, tendremos que dotar a nuestra ventana de dos espacios en los que podamos introducir la función y el rango (que se especificará mediante dos valores separados por una coma). Dichos espacios los ubicaremos en la parte inferior de la ventana:

Como se ve, hemos añadido una entrada («Entry») para la función a representar y otra para el rango (que se ubicará en la parte inferior derecha), acompañada de la etiqueta («Label») «RANGO DE ‘X'». A su vez, también hemos creado un botón («Button») «SET» con el que ordenaremos a nuestro programa que cree la gráfica a partir de los datos introducidos en la entrada. Una vez creados los elementos, pasaremos a ubicarlos en la ventana con el método «.pack()»:

Con ello, tendríamos creados (y ubicados) los elementos para la introducción de datos en el programa. Sin embargo, tal y como está ahora mismo, si introdujésemos una expresión en la entrada, y le diéramos al botón «SET» veríamos que no ocurre nada, y es que nos falta crear las funciones encargadas de tomar los datos introducidos y plasmarlos en la correspondiente gráfica.

Como lo que queremos es crear una gráfica que se actualice con los datos introducidos en las entradas (si abrir ni generar ventanas adicionales) usaremos una función (a la que llamaremos «animate()»), la cual, es la que vamos a pasar como argumento del método «FuncAnimation()», de modo que los cambios y operaciones que realice dicha función («animate()») son los que se plasmarán en el transcurso de la actualización (a intervalos regulares) que llevará a cabo FuncAnimation(). Para ver como funciona esto, como ejemplo, vamos a hacer que nuestra función de actualización «animate()» imprima un mensaje:

Y ejecutamos.

Así, vendría ser como un ciclo «while» con la diferencia de que aquí podemos alterar la actividad de la función durante su ejecución cíclica.

Otra función que vamos a necesitar (a la que llamaremos «represent()»), es aquella que tome los datos introducidos en las entradas (tanto de la destinada a la función a representar, así como la destinada al rango de «x») proporcionando las variables necesarias para que la gráfica sea posteriormente dibujada por la función «anim()» (que completaremos más adelante):

Antes de continuar, hemos de tener en cuenta que a la hora de introducir una entrada, el usuario introduzca, para , por ejemplo, representar el coseno de «x», «cos(x)». El problema con esto se encuentra en que para hacer dicho calculo con «numpy», habría que introducir «np.cos(x)». De modo que para evitar dicho problema, tendremos que hacer que el programa, recibiendo como entrada «cos(x)» lo traduzca internamente como «np.cos(x)». Para ello, haremos uso de una nueva función (de nombre «reemplazo()» mediante la que añadiremos «np.» a las funciones que lo requieran para ser calculadas por «numpy». Para usar tal función, nos valdremos, igualmente, de un diccionario («funciones{}») en el que incluiremos las funciones y su correspondiente cadena de reemplazo. La cadena resultante de esta función «reemplazo()» es la que finalmente guardaremos en la variable «graph_data», que emplearemos para dibujar la gráfica:

La otra entrada que va a tomar «represent()» es la correspondiente al rango de «X». Dado que más adelante vamos a necesitar tomar los dos valores del rango por separado, separaremos (usando «split()» empleando la coma «,» como separador) dichos valores («ran=rann.split(«,»)»). Puesto que vamos a dar la posibilidad de que el usuario no especifique ningún rango (ets.get()=»») estableceremos que dicha acción consistente en la separación se produzca solo si no se da tal condición («if ets.get()! =»»»).

Finalmente, haremos que esta función se ejecute al pinchar en el botón «SET», por ello añadiremos a dicho botón «command=repesent»:

Ya tenemos creada la función «represent()», encargada de tomar y preparar los datos de las entradas. Ahora le toca el turno a la función «animate()», que dibujará la gráfica y que se irá actualizando a intervalos regulares durante la ejecución. Dentro de la cual, empezaremos por la parte dedicada al rango:

Para empezar, la función distinguirá el hecho de que el usuario haya especificado un rango para «x» («act_rango=True») o no («act_rango=False»). En el primer caso, nuestro programa creará dos variables («lmin» y «lmax») que serán los limites mínimo y máximo del rango de «x», los cuales, se corresponden con las posiciones 0 y 1 de la lista «ran» generada en la función «represent()» ya vista. A su vez, para evitar resultados erróneos que pudieran derivarse del hecho de que en la entrada se introdujese un primer valor más alto que el segundo, el establecimiento del rango, con el método «np.arange()» solo se produzca en el caso de que, efectivamente el valor de «lmin» sea menor que el de «lmax» («if lmin<lmax:»). Una vez que se haya completado esta operación de establecimiento del rango crearemos una nueva variable («ul_ran») que almacenará el citado rango, por un motivo que veremos más adelante.

Otro posible error que podría darse es que el usuario introdujese un dato arbitrario que no tuviera nada que ver con el establecimiento de un rango (por ejemplo, una palabra cualquiera). Es por ello, que hemos hecho uso de la sentencia «try», de modo, que si el programa no pudiera funcionar con los datos proporcionados, se produjese una excepción («except») consistente en la muestra de una ventana de error, que crearemos introduciendo «messagebox.showwarning(«Error»,»Entrada no válida»)». En este caso se borrará la entrada («ets.delete(0,len(ets.get()))»), una vez cerrada la ventana de error, estableciéndose, automáticamente el último rango, válido, introducido (almacenado en «ul_ran»).

Lo visto, sería para el caso en el que se haya especificado rango, en caso contrario, este será de 1 a 1o, si se trata de una primera ejecución (en donde «ul_ran==»»») o será el correspondiente al último rango introducido (si «ul_ran!=»»»):

A continuación, nuestra función «animate()» pasará a trazar la gráfica correspondiente a la expresión que queremos representar, la cual, quedó definida en «represent()» y almacenada en la variable «graph_data»:

Puesto, que, al introducir la función a representar, cabe el mismo peligro que veíamos en el caso del rango, de introducir una cadena, imposible de ser tratada (con «eval»), usaremos una sentencia «try». De modo que si a expresión es apta, se almacenará en un variable «calculo_funcion», para acto seguido, ser representada gráficamente con el método «.plot()» para el valor actual de cada uno de los que vaya tomando la variable «x» («ax1.plot(x,calculo_funcion)»). Si la expresión no es apta («except»), el programa no empleará «graph_data». Simplemente representará los valores por defecto (si no se he hecho una representación correcta previamente) o dejará la última representación realizada con éxito.

Finalmente, para una mejor visibilidad, hemos marcado los ejes «x» e «y» con un tono de gris («gray») mediante los procedimientos «axhline()» y «axwline()» respectivamente:

Y con esto tendríamos creado nuestro graficador de funciones, que nos permitirá visualizar gráficas correspondientes a funciones sencillas combinando los recursos proporcionados por las librerías «tkinter», «matplotlib» y «numpy»:

Tenéis el código fuente en el siguiente enlace: https://github.com/antonioam82/graficador/blob/master/graficador.py

INCLUYENDO BARRA DE HERRAMIENTAS:

No obstante aún podemos hacer más, como por ejemplo, incluir la barra de herramientas de «matplotlib» (que nos permitirá hacer cosas tales como hacer zoom, mover la imagen o guardar la gráfica creada) en la parte inferior de nuestro graficador. Para ello, lo primero que haremos será importar «NavigationToolbar2Tk» para después crear el objeto con la variable «toolbar», tal y como se ve en la siguiente captura:

A su vez, para que la ejecución continuada no interfiera en el posible manejo de la barra de herramientas, necesitaremos que tras cada nueva actualización de la gráfica, se detenga la animación. Para los cual incluiremos el método «event_source.stop()» al final de la función «animate()»:

Sin embargo, una vez detenida la animación, necesitamos que esta se reanude posteriormente, si queremos dibujar una nueva gráfica. Es por ello que incluiremos el método «event_source.start()» en la función «represent()» (que se activará con el botón «SET»):

Esta versión mejorada del programa, que incluye la barra de herramientas de «matplotlib», puede verse en el siguiente enlace: https://github.com/antonioam82/graficador/blob/master/graficador_con_ toolbar.py

Saludos.

 

Antonio Alfonso MartínezProgramador y desarrollador autodidacta. Semanalmente publica en el blog El programador Chapuzas (wordpress) y también colaboro en las páginas “Código Comentado” y “Algoritmos MathPy” de Facebook.

 

 

Si quieres formar parte de la comunidad JustCodeIt, compartiendo información relevante sobre desarrollo Web, Mobile, Big Data o Blockchain puedes escribirnos a [email protected] .

Share this:

Leave a comment