Python Matplot animation embedding in Tkinter - python

I'm currently working on a visualisation of different sorting algorithms and am now attempting to embed the animations in a tkinker gui for ease of use. The animations are done with matplotlib and use bar charts. I'm currently struggling to get the graphs to be animated. I had it working without the gui but currently I get a static graph in the gui. Please could I have some advice on how to rectify this. Full code so far can be found at https://github.com/MGedney1/Sorting_Algorithm_Visualisation
Area of interest code:
import random
import time
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as tk
from tkinter import Frame,Label,Entry,Button
def bubble_sort(lst): #Bubble Sort
index = len(lst) - 1
while index >= 0:
test_index = index
while test_index >= 0:
if lst[index] < lst[test_index]:
temp = lst[index]
lst[index] = lst[test_index]
lst[test_index] = temp
test_index -= 1
yield lst
index -= 1
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self,master)
self.master = master
self.set_up_window()
def update_fig(self): #Update fig function
for self.rect, val in zip(self.rects,self.unordered): #Setting height of the rectangles
self.rect.set_height(val)
self.iteration[0] += 1
text.set_text("# of operations: {}".format(iteration[0]))
def set_up_window(self):
n,self.unordered = create_array()
title = 'Test'
generator = bubble_sort(self.unordered)
self.fig,self.ax = plt.subplots() #Creating axis and figure
self.bar_rects = self.ax.bar(range(len(self.unordered)), self.unordered, align="edge") #Creating the rectangular bars
self.ax.set_xlim(0, n) #Axis limits
self.ax.set_ylim(0, int(1.07 * n))
self.text = self.ax.text(0.02, 0.95, "", transform=self.ax.transAxes) #Number of operations counter
self.iteration = [0]
self.anim = animation.FuncAnimation(self.fig, func=self.update_fig, frames=generator, interval=1,repeat=False) #Creating the animatio
self.canvas = FigureCanvasTkAgg(self.fig, master=root)
self.canvas.get_tk_widget().grid(column=0,row=1)
if __name__ == '__main__':
root = tk.Tk()
root.geometry("700x400")
app = Window(root)
tk.mainloop()

Related

How can an interactive subplot in Tkinter be improved in terms of speed?

I am currently working on a program that I am creating with Tkinter. Thereby large matrices (signals) are read in, which I represent as an image. In addition, I would like to display the signal at the point X (red vertical line) in the adjacent plot (interactive).
# Imports
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import numpy as np
from tkinter import *
# Global: Selected points with cursor
points = []
# Cursor
class Cursor:
def __init__(self, ax):
self.ax = ax
self.background = None
self.horizontal_line = ax.axhline(color='r', lw=0.8, ls='--')
self.vertical_line = ax.axvline(color='r', lw=0.8, ls='--')
self._creating_background = False
ax.figure.canvas.mpl_connect('draw_event', self.on_draw)
def on_draw(self, event):
self.create_new_background()
def set_cross_hair_visible(self, visible):
need_redraw = self.horizontal_line.get_visible() != visible
self.horizontal_line.set_visible(visible)
self.vertical_line.set_visible(visible)
return need_redraw
def create_new_background(self):
if self._creating_background:
return
self._creating_background = True
self.set_cross_hair_visible(False)
self.ax.figure.canvas.draw_idle()
self.background = self.ax.figure.canvas.copy_from_bbox(self.ax.bbox)
self.set_cross_hair_visible(True)
self._creating_background = False
def on_mouse_move(self, event, mode: str, matrix=None):
if self.background is None:
self.create_new_background()
if not event.inaxes:
need_redraw = self.set_cross_hair_visible(False)
if need_redraw:
self.ax.figure.canvas.restore_region(self.background)
self.ax.figure.canvas.blit(self.ax.bbox)
else:
self.set_cross_hair_visible(True)
x, y = event.xdata, event.ydata
if mode == "both":
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.ax.figure.canvas.restore_region(self.background)
self.ax.draw_artist(self.horizontal_line)
self.ax.draw_artist(self.vertical_line)
elif mode == "horizontal":
self.ax.cla()
self.ax.plot(matrix[:, int(x)], range(0, matrix.shape[0], 1))
self.ax.figure.canvas.draw_idle()
self.horizontal_line.set_ydata(y)
self.ax.figure.canvas.restore_region(self.background)
self.ax.draw_artist(self.horizontal_line)
self.ax.figure.canvas.blit(self.ax.bbox)
# Graphical User Interface
class ToolGUI:
def __init__(self, master):
self.master = master
# Matrix (Example)
self.matrix = np.random.rand(3000, 5000)
# Subplots
self.fig = plt.figure(constrained_layout=True)
self.spec = self.fig.add_gridspec(5, 6)
self.ax_main = self.fig.add_subplot(self.spec[:, :-1])
self.ax_main.imshow(self.matrix, cmap='gray', aspect='auto')
self.ax_right = self.fig.add_subplot(self.spec[:, -1:], sharey=self.ax_main)
self.ax_right.get_yaxis().set_visible(False)
# Canvas - Drawing Area
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
self.canvas.get_tk_widget().grid(column=0, row=0, sticky=NSEW)
# Cursor with crosshair
self.cursor_main = Cursor(self.ax_main)
self.fig.canvas.mpl_connect('motion_notify_event', lambda event: self.cursor_main.on_mouse_move(event, mode="both"))
self.cursor_right = Cursor(self.ax_right)
self.fig.canvas.mpl_connect('motion_notify_event', lambda event: self.cursor_right.on_mouse_move(event, mode="horizontal", matrix=self.matrix))
# Update Canvas
self.canvas.draw() # Update canvas
# Create root window
root = Tk()
# Root window title
root.title("Tool")
# Create GUI
my_gui = ToolGUI(root)
# Execute Tkinter
root.mainloop()
This example is only a small part of my program. In my full program, for example, certain points are picked out manually. With the help of the interactive plot a more exact selection of such points is possible.
Unfortunately, the program runs very slowly due to the use of this interactive plot. Since I haven't been working with Python for too long, I would appreciate any suggestions for improvement!
Thanks in advance! - Stefan

My matplotlib animation is slow, how to optimize it?

I am doing an EEG in python using the matplotlib library,
I generate random informations and I display them in a Tkinter window,
I want to update the animation 10 times per seconds,
So 10 updates should last 1 second, right ?
But instead the animation lasts between 1.1 and 1.3 seconds ...
I guess this is an optimization issue ?
I will be very grateful if you could help me !
How my EEG looks like :
my matplotlib's EEG at 17 seconds
my matplotlib's EEG at 29 seconds
Here's my code (main.py):
import tkinter as tk
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from Sources.Model.grapheEEG5 import grapheEEG5
from matplotlib.animation import FuncAnimation
HAUTEUR = 768
LARGEUR = 1366
root = tk.Tk()
root.title("Interface logicielle d'acquisition")
fenetre = tk.Canvas(root, height=HAUTEUR, width=LARGEUR)
fenetre.pack()
fig = plt.Figure(figsize = (10, 6), dpi = 100)
cadreMilieu = tk.Frame(fenetre, bg='white')
cadreMilieu.place(relx=0.1, rely=0.1, relwidth=0.8, relheight=0.8)
canvas = FigureCanvasTkAgg(fig, master = cadreMilieu).get_tk_widget().pack()
grapheDeroulant = grapheEEG5(8, fig, None, "EEG", "temps (en secondes)", "capteurs")
aniPoints = FuncAnimation(fig, grapheDeroulant.animation, cache_frame_data = False,
save_count = 0, frames=None, blit=False, interval=100, repeat=False)
root.mainloop()
And my class (grapheEEG5.py):
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import random as random
import time
class grapheEEG5:
#variables
axe = None
figure = None
toolbar = None
tailleDonnees = None
nbEEG = None
compteur = 0
#parametres
echelle = 1.0/10
periodeTrame = 0.1
boucle = 0
dureeSecondesFigure = 20
#courbes
ligneCurseur = None
ordonnees = None
ordonneesFantome = None
#coords curseur
cursorY = None
#coords vrais points
xVraiPoints = []
yVraiPoints = []
def __init__(self, nbEEG, figure, toolbar, titre, nomx, nomy, grid = None):
self.nbEEG = nbEEG
self.cursorY = [0, -self.nbEEG-1]
self.figure = figure
self.tailleDonnees = int(self.dureeSecondesFigure / self.periodeTrame)
print(self.tailleDonnees, "donnée(s)")
self.axe = self.figure.add_subplot(111)
self.axe.grid(ls="--", lw=0.5)
self.axe.set_title(titre)
self.axe.set_xlabel(nomx)
self.axe.set_ylabel(nomy)
self.axe.set_xlim(0, self.dureeSecondesFigure)
self.axe.xaxis.set_major_locator(plt.MaxNLocator(self.dureeSecondesFigure))
self.axe.set_ylim(-self.nbEEG-1,0)
self.axe.get_yaxis().set_visible(False)
for i in range(self.nbEEG):
self.xVraiPoints.append([None]*self.tailleDonnees)
self.yVraiPoints.append([None]*self.tailleDonnees)
self.ordonnees = []
self.ordonneesFantome = []
for i in range(self.nbEEG):
self.ordonnees.append(self.axe.plot([], [], lw=0.75, color="black", label="nouvelles valeurs")[0])
self.ordonneesFantome.append(self.axe.plot([], [], lw=0.75, color="grey", label="anciennes valeurs")[0])
self.ligneCurseur, = self.axe.plot([], [], color="red", label="curseur")
if toolbar != None:
self.toolbar = toolbar
self.toolbar.update()
for i in range(nbEEG):
self.axe.text(-30*self.periodeTrame, -i-1, "capteur " + str(i+1))
self.figure.canvas.draw()
def animation(self, i):
temps = self.compteur*self.periodeTrame
for _ in range(self.nbEEG):
self.xVraiPoints[_][self.compteur] = temps
indexEeg = -_-1
y = indexEeg + (random.randint(-3,3) * self.echelle)
self.yVraiPoints[_][self.compteur] = y
self.ordonnees[_].set_data(self.xVraiPoints[_], self.yVraiPoints[_])
cursorX = [temps, temps]
self.ligneCurseur.set_data(cursorX, self.cursorY)
self.compteur += 1
if self.compteur >= self.tailleDonnees:
self.compteur = 0
return self.ordonnees
I printed the time when i call my animations referenced in funcAnimation and it seems that it is never the exact time, for example if i put interval = 0.5
then my script will call it after 0.46 sec or 0.56, and this is this approximation that was creating delay. Now i am using a manual animation and i am calling it with a method that does not drift and it works perfectly.

Xtick label Rotation resets after FigureCanvasTkAgg.draw()

I'm creating a GUI in Tkinter that has an embedded matplotlib graph. I change the contents of the graph based on a couple of Radiobuttons. These Radiobuttons are linked to a function which redefines the data in the graph, and ends with a call to self.canvas.draw(). I used Unable to update Tkinter matplotlib graph with buttons and custom data substantially, and I got it to work perfectly: the graph is updated with the desired new data.
The problem I run into is that (I'm using dates for the x-tick labels) after the redraw function is called, the x-tick labels get rotated back to horizontal, causing them to overlap. Below is a simplified form of my application, which has no dependencies so can be run directly.
import tkinter
from tkinter import *
from tkinter.ttk import *
import csv
import datetime as dt
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class MainApp:
def __init__(self,parent):
self.RightFrame = LabelFrame(parent,text='RightFrame')
self.RightFrame.pack(side='right')
self.plot_fig=plt.figure(figsize=(4,4),dpi=100)
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
self.fig.autofmt_xdate(rotation=30) ### Sets tick rotation Initially
self.ax.fmt_xdata=mdates.DateFormatter('%Y-%m-%d')
self.ax.hold(False)
self.canvas = FigureCanvasTkAgg(self.fig,master=self.RightFrame)
self.canvas.show()
self.canvas.get_tk_widget().pack(side=TOP)
self.plot_radio_frame = LabelFrame(self.RightFrame)
self.plot_radio_frame.pack(side=BOTTOM)
self.plot_time_radio_var = IntVar()
self.plot_time_radio_var.set(7)
modes = [('1 Week',3),('1 Month',4),('1 Year',5),('5 Years',6),('All',0)]
for text,value in modes:
b= Radiobutton(self.plot_radio_frame,text=text,variable=self.plot_time_radio_var,value=value)
b.pack(side=LEFT)
self.plot_time_radio_var.trace('w',self.plotting_function)
def plotting_function(self, varname, elementname, mode):
plot_time = self.plot_time_radio_var.get()
try:
data_hist = [['2016-12-16',116.4700],
['2016-12-19',115.8000],
['2016-12-20',116.7400],
['2016-12-21',116.8000],
['2016-12-22',116.3500],
['2016-12-23',115.5900],
['2016-12-27',116.5200],
['2016-12-28',117.5200],
['2016-12-29',116.4500],
['2016-12-30',116.6500]
]
days = int(plot_time)
spacing = 1
dates = [line[0] for line in data_hist[-days::spacing]]
new_dates = [dt.datetime.strptime(d,'%Y-%m-%d').date() for d in dates]
Y_DATA = [line[1] for line in data_hist[-days::spacing]]
#print(adj_close)
self.curve = self.ax.plot(new_dates,Y_DATA)
self.canvas.draw()
except Exception as e:
print(str(e))
root = Tk()
MainApp(root)
root.mainloop()

Tkinter updating matplotlib figure from different tk.Button in another frame

I'm building a GUI using Tkinter for the first time and have run into a problem updating the data in a Matplotlib Figure using a button in a different frame. Below is some generalized code to show the error I'm getting.
from Tkinter import *
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class testApp():
def app(self):
self.root = Tk()
self.create_frame1()
self.create_frame2()
self.root.mainloop()
def create_frame1(self):
frame1 = Frame(self.root)
frame1.grid(row=0)
(x, y) = self.create_data()
f = plt.Figure(figsize = (5,2), dpi=100)
a = f.add_subplot(111)
lines1, = a.plot(x, y)
f.tight_layout()
canvas = FigureCanvasTkAgg(f, frame1)
canvas.get_tk_widget().grid()
def create_frame2(self):
frame2 = Frame(self.root)
frame2.grid(row=1)
reEval = Button(frame2, text = "Reevaluate", command = lambda: self.reRand()).grid(sticky = "W")
def reRand(self):
(x, y) = self.create_data()
ax = self.root.frame1.lines1
ax.set_data(x, y)
ax.set_xlim(x.min(), x.max())
ax.set_ylim(y.min(), y.max())
self.root.frame1.canvas.draw()
def create_data(self):
y = np.random.uniform(1,10,[25])
x = np.arange(0,25,1)
return (x, y)
if __name__ == "__main__":
test = testApp()
test.app()
When I run this code, I get an error:
AttributeError: frame1
I think my problem stems from how I am referencing the frame containing the figure, itself, so I'm fairly certain this problem is arising from my lack of Tkinter experience. Any help would be greatly appreciated.
This is more to do with using Python classes than Tkinter. All you really need to do is change all frame1 in create_frame1 to be self.frame1. Similarly in reRand, self.root.frame1 becomes self.frame1.
As it is the name "frame1" doesn't exist beyond the end of create_frame1, but if you save it as an attribute of self you can access it later.

Interactive plot based on Tkinter and matplotlib

Dear programmming communauty,
I am trying to perform a "interactive plot" based on Tkinter and pylab.plot in order to plot 1D values. The abssissa are a 1D numpy array x and the ordonates values are in a multidimension array Y, eg.
import numpy
x = numpy.arange(0.0,3.0,0.01)
y = numpy.sin(2*numpy.pi*x)
Y = numpy.vstack((y,y/2))
I want to display y or y/2 (the elements of Y matrix) according to x and change between them with 2 buttons left and right (in order to go to more complex cases). Usually I create some functions like the following to plot graphs.
import pylab
def graphic_plot(n):
fig = pylab.figure(figsize=(8,5))
pylab.plot(x,Y[n,:],'x',markersize=2)
pylab.show()
To add two buttons to change the value of nparameter, I have tried this without success :
import Tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class App:
def __init__(self,master):
# Create a container
frame = Tkinter.Frame(master)
frame.pack()
# Create 2 buttons
self.button_left = Tkinter.Button(frame,text="<",command=self.decrease)
self.button_left.pack(side="left")
self.button_right = Tkinter.Button(frame,text=">",command=self.increase)
self.button_right.pack(side="left")
self.canvas = FigureCanvasTkAgg(fig,master=self)
self.canvas.show()
def decrease(self):
print "Decrease"
def increase(self):
print "Increase"
root = Tkinter.Tk()
app = App(root)
root.mainloop()
Can someone help me to understand how to perform such kind of feature ? Many thanks.
To change the y-values of the line, save the object that's returned when you plot it (line, = ax.plot(...)) and then use line.set_ydata(...). To redraw the plot, use canvas.draw().
As a more complete example based on your code:
import Tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
class App:
def __init__(self, master):
# Create a container
frame = Tkinter.Frame(master)
# Create 2 buttons
self.button_left = Tkinter.Button(frame,text="< Decrease Slope",
command=self.decrease)
self.button_left.pack(side="left")
self.button_right = Tkinter.Button(frame,text="Increase Slope >",
command=self.increase)
self.button_right.pack(side="left")
fig = Figure()
ax = fig.add_subplot(111)
self.line, = ax.plot(range(10))
self.canvas = FigureCanvasTkAgg(fig,master=master)
self.canvas.show()
self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
frame.pack()
def decrease(self):
x, y = self.line.get_data()
self.line.set_ydata(y - 0.2 * x)
self.canvas.draw()
def increase(self):
x, y = self.line.get_data()
self.line.set_ydata(y + 0.2 * x)
self.canvas.draw()
root = Tkinter.Tk()
app = App(root)
root.mainloop()

Categories

Resources