# Function Plotter GUI in Tkinter and Matplotlib

I have been working on plotting time varying functions in Python as my most recent project, and would love input on optimization, proper Tkinter form, and anything that stands out like a sore thumb.

To get technical details out of the way, this code has been tested and works on my home box running Linux Mint 18 (4.4.0-21-generic kernel) with Python 3.5.2. It uses matplotlib, numpy, sympy, and Tkinter. The code supplied will be verbatim what I currently have, saved as 3 separate files. The import structure is definitely something I need clarification on!

For use, one can switch between graphs stored via the dropdown box and clicking the 'Make' button. New graphs can be entered by typing in first an initial function (in numpy format, such as np.sin(x)) varying in space (x), and a varying function (eg np.sin(x+i)), varying in space (x) and time (i), and pressing the respective add button. Time progression can be stopped and started via two buttons.

### dictionaries.py

from sympy import *
from sympy.parsing.sympy_parser import parse_expr
import Tkinter as Tk

"""
These dictionaries serve the purpose of storing the space varying and
time varying functions to be plotted.
"""

initFuncts = {0: '(np.exp(-0.5 * x ** 2) +'
'2 * x * np.exp(-0.5 * x ** 2)) ** 2',
1: 'x**2'}
varyFuncts = {0: '((1 / (4 * np.pi)) ** (1 / 4)) * '
'((np.exp(-0.5 * x ** 2) * np.cos(0.5 * i) + '
'2 * x * np.exp(-0.5 * x ** 2) *'
'np.cos(1.5 * i)) ** 2)',
1: 'x**2 + np.cos(i)'}

initFuncts[len(initFuncts)] = textField.get()
expr = parse_expr(initFuncts[len(initFuncts)-1].replace('np.', ''))
strings.set('')
for key in initFuncts:
label=str(key),
command=Tk._setit(strings, str(key)))

varyFuncts[len(varyFuncts)] = textField.get()


### animationObj.py

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import Tkinter as Tk, numpy as np
import matplotlib.pyplot as plt, matplotlib.animation as animation
import dictionaries as dicts

class animationObj:
def __init__(self, mainframe, num, x, timeString):
"""
Create instance of animationObj class, enabling the animation of
matplotlib based figures.

Keyword arguments:
mainframe -- the frame the animation will appear in
num -- the function number in the dictionaries
x -- spacial range of the animation
timeString -- string which will report current time to user
"""

self._master = mainframe  # set frame
self.timeString, self.num, self.x = timeString, num, x
self.fig = plt.Figure()  # create pyplot figure
# add figure to canvas and connect canvas to frame
self.canvas = FigureCanvasTkAgg(self.fig, master=self._master)
# get widget then pack
self.tkwidget = self.canvas.get_tk_widget()
self.tkwidget.pack()
# add subplot to actually animate on
# generate initial plot, note the syntax: 'self.line,', comma
# after variable means returning as a tuple
self.line, = self.ax.plot(self.x,
eval(dicts.initFuncts[self.num]))
# set bounds on plot
self.ax.axis([-6, 6, -5, 5])
# animate the animation
self.ani = animation.FuncAnimation(self.fig, self.animate,
np.arange(0, 200, 0.01),
interval=1, blit=False)

def animate(self, i):
"""
Animation function which executes every 'interval given in the
FuncAnimation instantiation.

Keyword arguments:
i -- the current time
"""
# must create a (not technically called this...) 'local global'
# version of x because set_ydata expects a global variable x
x = self.x
self.timeString.set('t = ' + '{:3.2f}'.format(i))  # report time
# generate plot at next time interval
self.line.set_ydata(eval(dicts.varyFuncts[self.num]))
# return line tuple
return self.line,

def stopIt(self):
"""Pause the current animation shown."""
self.ani.event_source.stop()

def startIt(self):
"""Resume the current animation shown."""
self.ani.event_source.start()

def removeIt(self):
"""Remove the current animation from the window."""
self.tkwidget.pack_forget()
self.ani.event_source.stop()

"""
Add a new plot to the plotting window.

Keyword arguments:
num -- the function number in the dictionaries
"""
if (num in dicts.initFuncts) and (num in dicts.varyFuncts):
self.removeIt()  # remove old plot
x = self.x  # same as before, expects a global x
i = 0  # reset time to zero
self.num = int(num)  # update instance variable
#reinitialize all needed components
self.fig = plt.Figure()
self.canvas = FigureCanvasTkAgg(self.fig, master=self._master)
self.tkwidget = self.canvas.get_tk_widget()
self.tkwidget.pack()
self.line, = self.ax.plot(self.x,
eval(dicts.initFuncts[self.num]))
self.ax.axis([-6, 6, -5, 5])
self.ani = animation.FuncAnimation(self.fig, self.animate,
np.arange(0, 200, 0.01),
interval=1, blit=False)


### main.py

import Tkinter as Tk
import numpy as np
import animationObj
import dictionaries

def _quit(mast):
"""
Quits the entire application.

Keyword arguments:
mast -- the root Tk instance
"""
mast.quit()
mast.destroy()

root = Tk.Tk()  # root Tk instance
t1 = Tk.Toplevel(root)
t2 = Tk.Toplevel(root)  # Tk Toplevel instance to separate windows

f1 = Tk.Frame(t1)
f2 = Tk.Frame(t2)
f = Tk.Frame(root)
label1 = Tk.Label(f, text="Superposition State").pack()

# set each frames' geometry
t2.geometry("%dx%d+%d+%d" % (250, 300, 650, 625))
t1.geometry("%dx%d+%d+%d" % (250, 300, 650, 300))
root.geometry("%dx%d+%d+%d" % (500, 500, 150, 300))

options = Tk.StringVar()
options.set('0')
w = Tk.OptionMenu(f1, options, '0', '1')
w.pack(side=Tk.BOTTOM)

# string to display time
timeString = Tk.StringVar()
timeString.set('t = 0')
l = Tk.Label(f1, textvariable=timeString, font=("Courier", 24))

# make and pack quit button
button = Tk.Button(master=f1, text='Quit',
command=lambda: _quit(root), height=2)
button.pack(side=Tk.BOTTOM)

# pack time string
l.pack(side=Tk.TOP)

x = np.arange(-6, 6, 0.005)  # range of x values
aniObj = animationObj.animationObj(f, 0, x, timeString)

# other buttons
button3 = Tk.Button(master=f1, text='Make',
stopIt = Tk.Button(master=f1, text='Stop', command=aniObj.stopIt, height=1)
startIt = Tk.Button(master=f1, text='Start',
command=aniObj.startIt, height=1)

# pack frames
f.pack()
f1.pack()
f2.pack()

# pack buttons
button3.pack(side=Tk.BOTTOM)
stopIt.pack(side=Tk.LEFT)
startIt.pack(side=Tk.LEFT)

e = Tk.Entry(f2)

button4 = Tk.Button(master=f2, text='Add Initial Function',
height=1)
button5 = Tk.Button(master=f2, text='Add Varying Function', command=lambda:
button4.pack(side=Tk.BOTTOM)
button5.pack(side=Tk.BOTTOM)
e.pack(side=Tk.TOP)

# execute main loop on base instance
root.mainloop()


Again I would like to thank any advice and criticisms offered up in advance!

There are a lot of PEP 8 issues in your code. I recommend reading through that document.

It is recommend to avoid using eval, as it makes your code harder to maintain and has lots of security issues.

Instead, it is better to represent your expressions symbolically with SymPy, then use sympy.lambdify to convert them to numerical functions. So for instance, your initFuncts could be (by the way, if your dict keys are just 0 and 1, why not just use a list?) could be

import sympy as sym
x = sym.symbols('x')
initFuncts = [(sym.exp(-x**2/2) + 2*x*sym.exp(-x**2/2))**2, x**2]


(I've also cleaned up the spaces, and used rational numbers instead of floats).

Then you could convert this to a numpy function later with lambdify. For example,

f = lambdify(x, initFuncts[0], 'numpy')


creates a function f which evaluates the first expression as a NumPy function.

• How would one go about converting a raw expression from the user to the sympy expression needed in the lists/dictionaries? I used dictionaries so that eventually the user can input names which can be displayed in the dropdown as well. Thanks for the insight though, lambdify certainly helps! – logical123 Apr 2 '17 at 20:44
• You can use sympy.sympify to convert a string into a sympy expression. – asmeurer Apr 2 '17 at 21:31