I would like to make an equivalent of the function FuncAnimation from matplotlib.animation, in which I could control the current plotted data using the scrollbar.
Say you have a data array which contains data points to be plotted at each time i. When using FuncAnimation, you first need to define a function ( here animate(i) ) which will be called for each time i = 1 to len(data[:,0]) :
def animate(i):
ax.plot(data[i,:])
anim = FuncAnimation(fig, animate, interval=100, frames=len(data[:,0]))
plt.draw()
plt.show()
but you cannot control the time i, like with a play/stop functionality. What I would like to do is to call the function animate(i), with i being the position of the scrollbar.
I found this example ( using the events from matplotlib:
https://matplotlib.org/3.2.1/users/event_handling.html )
but the mpl_connect doesn't have a "scrollbar_event".
import tkinter
from random import randint
import matplotlib as plt
import numpy as np
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
#create figure
fig = Figure(figsize=(5, 4), dpi=100)
ax = fig.add_axes([0,0,1,1])
ax.imshow(np.array([[0,10],[23,40]]))
#create canvas with figure
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
def on_key_press(event):
ax.clear()
ax.imshow(np.array([[randint(0,30),randint(0,30)],[randint(0,30),randint(0,30)]]))
canvas.draw_idle()
key_press_handler(event, canvas)
print("you pressed {}".format(event.key))
#connect canvas to event function
canvas.mpl_connect("key_press_event", on_key_press)
def _quit():
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
button = tkinter.Button(master=root, text="Quit", command=_quit)
button.pack(side=tkinter.BOTTOM)
tkinter.mainloop()
Actually the scroll functionality is given by matplotlib widgets !!
The example below works fine :
import matplotlib
import tkinter as Tk
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from myPytHeader import *
matplotlib.use('TkAgg')
root = Tk.Tk()
root.wm_title("Embedding in TK")
fig = plt.Figure(figsize=(8, 6))
canvas = FigureCanvasTkAgg(fig, root)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
nDt = nbLines("grid.dat")
nDx = nbGridPoints("grid.dat")
grid = np.zeros( (nDt,nDx) ) ; loadData("grid.dat", grid)
valu = np.zeros( (nDt,nDx) ) ; loadData("valu.dat", valu)
ax=fig.add_subplot(111)
fig.subplots_adjust(bottom=0.25)
ax_time = fig.add_axes([0.12, 0.1, 0.78, 0.03])
s_time = Slider(ax_time, 'Time', 0, nDt, valinit=0, valstep=1)
def update(val):
frame = int(s_time.val)
ax.clear()
ax.set(xlim=(-0.05, 1.05), ylim=(-0.05, 1.25))
ax.grid()
ax.scatter(grid[frame,:], valu[frame,:], color='b', marker='.')
fig.canvas.draw_idle()
s_time.on_changed(update)
Tk.mainloop()
After all these years I've found solutions to my problems here, I am in debt !!!
Here is the final solution I came with.
I hope it can be useful to someone someday somehow !!
import matplotlib
import numpy as np
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# load data
nDt = 1000
nDx = 400
grd = np.zeros( (nDt,nDx) )
val = np.zeros( (nDt,nDx) )
for t in np.arange(nDt):
for x in np.arange(nDx):
grd[t,x] = x / nDx
val[t,x] = (x / nDx) * (t/nDt) * np.sin(10 * 2*np.pi * (t-x)/nDt)
matplotlib.use('TkAgg')
root = tk.Tk()
root.wm_title("Embedding in TK")
fig = plt.Figure(figsize=(8, 6))
canvas = FigureCanvasTkAgg(fig, root)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
ax=fig.add_subplot(111)
fig.subplots_adjust(bottom=0.25)
ax.set(xlim=(-0.05, 1.05), ylim=(-1.05, 1.05))
ax.grid()
scat = ax.scatter(grd[0,:], val[0,:], color='b', marker='.')
ax_time = fig.add_axes([0.12, 0.1, 0.78, 0.03])
s_time = Slider(ax_time, 'Time', 0, nDt, valinit=0, valstep=1)
i_anim = 0
i_relative = 0
i_current = 0
def updateGraph(i):
y_i = val[i,:]
scat.set_offsets(np.c_[grd[i,:], y_i])
def updateFromAnim(i):
global i_anim
global i_current
global i_relative
i_anim = i
i_current = i + i_relative
s_time.set_val(i_current)
updateGraph(i_current)
def updateFromScroll(val):
global i_anim
global i_current
global i_relative
i_relative = int(s_time.val) - i_anim
i_current = int(s_time.val)
updateGraph(i_current)
def onClick():
global anim_running
if anim_running:
anim.event_source.stop()
anim_running = False
else:
anim.event_source.start()
anim_running = True
start_button = tk.Button(root, text="START/STOP", command=onClick)
start_button.pack()
anim_running = True
anim = FuncAnimation(fig, updateFromAnim, interval=100, frames=nDt)
s_time.on_changed(updateFromScroll)
tk.mainloop()
Related
I'm doing a tkinter GUI for my program and I have to display some real time data. I made a simple program (below) to demonstrate my problem on a simple case. I'm actually plotting some data every iteration of my for loop so I can observe data while the program in still calculating. Note that the real program si calculating a bit slower and have more iterations.
Now I would like to add 2 buttons (one to pause the program and one to continue) and a label (diplay variable k so i know where my program is), but I am unable to do it.
I've already lost a lot of time on it so if anyone have a hint or a solution i would love to see it.
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
from matplotlib import style
def func_A(a, x):
import numpy
data_x = numpy.arange(0, x)
data_y = a * numpy.sin(data_x/5)
return data_x, data_y
a = 1
root = tk.Tk()
root.title("Graph")
root.geometry("800x400")
fig = plt.figure(figsize=(5, 5), dpi=100)
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
plt.grid("both")
style.use("ggplot")
for k in range(0, 100):
data_x, data_y = func_A(a, k)
print("iteration", k)
print("data_x", data_x)
print("data_y", data_y)
if k == 0:
ax1 = plt.subplot(111)
line1, = ax1.plot([0], [0])
else:
line1.set_xdata(data_x)
line1.set_ydata(data_y)
ax1.set_ylim([-1, 1])
ax1.set_xlim([0, 100])
plt.grid("both")
canvas.draw()
canvas.flush_events()
root.mainloop()
To add pause/resume function:
create a frame to hold the progress label and the two buttons: pause and resume
create a tkinter BooleanVar() to store the pause/resume state
move the update plot code inside a function, e.g. update_plot()
use .after() to replace the for loop to call update_plot() periodically
inside update_plot(), check the pause/resume state to determine whether to update the plot or not
Below is a modified example based on your code:
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
from matplotlib import style
import matplotlib
matplotlib.use("Agg")
root = tk.Tk()
root.title("Graph")
#root.geometry("800x400")
# progress label, pause and resume buttons
frame = tk.Frame(root)
frame.pack(fill="x", side=tk.TOP)
progress = tk.Label(frame)
progress.pack(side="left")
is_paused = tk.BooleanVar() # variable to hold the pause/resume state
tk.Button(frame, text="Pause", command=lambda: is_paused.set(True)).pack(side="right")
tk.Button(frame, text="Resume", command=lambda: is_paused.set(False)).pack(side="right")
# the plot
fig = plt.figure(figsize=(10, 5), dpi=100)
canvas = FigureCanvasTkAgg(fig, master=root)
toolbar = NavigationToolbar2Tk(canvas, root)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
plt.grid("both")
style.use("ggplot")
a = 1
ax1 = plt.subplot(111)
line1, = ax1.plot([0], [0])
def func_A(a, x):
import numpy
data_x = numpy.arange(0, x)
data_y = a * numpy.sin(data_x/5)
return data_x, data_y
# function to update ploat
def update_plot(k=0):
if not is_paused.get():
progress["text"] = f"iteration: {k}"
data_x, data_y = func_A(a, k)
#print("iteration", k)
#print("data_x", data_x)
#print("data_y", data_y)
line1.set_xdata(data_x)
line1.set_ydata(data_y)
ax1.set_ylim([-1, 1])
ax1.set_xlim([0, 100])
plt.grid("both")
canvas.draw()
canvas.flush_events()
k += 1
if k <= 100:
# update plot again after 10ms. You can change the delay to whatever you want
root.after(10, update_plot, k)
update_plot() # start updating plot
root.mainloop()
Need help with matplotlib in tkinter, can't seem to get the entry to then show of the correct graph.
I would like to type in 'x**2' and show the graph for this function but it doesn't seem to work.
If anyone could help me out, I would appreciate it. Thanks in advance.
import tkinter as tk
from tkinter import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import numpy as np
import matplotlib
from matplotlib.figure import Figure
matplotlib.use('TkAgg')
screen = tk.Tk()
screen.title('Function Graph')
screen.geometry('350x200')
function = Entry(screen, width = 20)
function.place(x=123, y=92)
f_label = Label(screen, text='Plot Function: ')
f_label.place(x=145, y= 70)
def plot_g():
x = np.linspace(-3,3,100)
y = function.get()
new_w = tk.Toplevel(screen)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
plt.grid()
plt.plot(x,y)
canvas = FigureCanvasTkAgg(fig, master=new_w)
canvas.draw()
canvas.get_tk_widget().pack(side = 'bottom', fill= BOTH, expand=True)
toolbar = NavigationToolbar2Tk(canvas, new_w)
toolbar.update()
f_button = Button(screen, text= 'Enter', command = plot_g)
f_button.place(x=160, y=120)
screen.mainloop()
I am trying to graph readings received from a weight scale connected to Arduino. The Arduino using bluetooth sends data to RPi and I successfully made a live GUI. The issue is, I am trying to have a SpinBox or some sort of counter that can adjust the interval of at which the graph updates. I have tried using the variables for the interval in the FuncAnimation but it does not seem to work.
I have brute forced and noticed that once the code is executed, the interval is set to the initial value it receives and sticks to it. While loop does not work well with the objective I have to achieve. I have attached the code below too. Please note I am a beginner in Python.
import serial
from tkinter import *
from tkinter import ttk
from matplotlib import pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import style
from matplotlib.figure import Figure
import time
import read_arduino
ser = serial.Serial('/dev/rfcomm0', 115200)
ser.flushInput()
root = Tk()
root.geometry('800x400+200+100')
root.title('This is my root window')
tab_control = ttk.Notebook(root)
tab1 = ttk.Frame(tab_control)
tab2 = ttk.Frame(tab_control)
tab_control.add(tab1, text='First')
tab_control.add(tab2, text='Second')
# var =IntVar()
# var.set(300)
# incr=1000
# spin = Spinbox(tab1, from_=200, to=1000, width=5, textvariable=var)
xar = [0]
yar = [0]
style.use('ggplot')
fig = plt.figure(figsize=(3, 2.5))
plt.xlabel("Time")
plt.ylabel("Weight")
ax1 = fig.add_subplot(1,1,1)
ax1.set_aspect("auto")
fig.subplots_adjust(wspace=0, hspace=0)
ax1.set_ylim(0, 100)
line, = ax1.plot(xar, yar, 'blue',marker='x')
plt.tight_layout()
dum= StringVar()
lbl = Label(tab1, textvariable=dum).grid(row=2,column=2)
def animate(i):
x=get_avg()
yar.append(x)
xar.append(i)
line.set_data(xar, yar)
ax1.set_xlim(0, i+1)
dum.set(x)
def get_avg():
n = 60
val=str(0.0)
for n in range(1,60):
val=str(ser.readline(),"utf-8").strip("\n")
return val
plotcanvas = FigureCanvasTkAgg(fig, tab1)
plotcanvas.get_tk_widget().grid(row=5,column=0)
ani = animation.FuncAnimation(fig, animate, interval=1000, blit=False)
tab_control.pack(expand=1, fill='both')
root.mainloop()
I have created this plot in pyplot which has a slider to view a certain range of the data.
import random
import matplotlib
import tkinter as Tk
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
y_values = [random.randrange(20, 40, 1) for _ in range(40)]
x_values = [i for i in range(40)]
l, = plt.plot(x_values, y_values)
plt.axis([0, 9, 20, 40])
ax_time = plt.axes([0.12, 0.1, 0.78, 0.03])
s_time = Slider(ax_time, 'Time', 0, 30, valinit=0)
def update(val):
pos = s_time.val
ax.axis([pos, pos+10, 20, 40])
fig.canvas.draw_idle()
s_time.on_changed(update)
#plt.show()
matplotlib.use('TkAgg')
root = Tk.Tk()
root.wm_title("Embedding in TK")
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
Tk.mainloop()
The problem is, it seems unresponsive when embedded in a tkinter window, although when I show it with plt.show() (meaning the code after this, is commented) works correctly. What is the right way to do this?
I figured this one out. The figure needs to be added to the canvas before creating the plot.
import random
import matplotlib
import tkinter as Tk
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
matplotlib.use('TkAgg')
root = Tk.Tk()
root.wm_title("Embedding in TK")
fig = plt.Figure()
canvas = FigureCanvasTkAgg(fig, root)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
ax=fig.add_subplot(111)
fig.subplots_adjust(bottom=0.25)
y_values = [random.randrange(20, 40, 1) for _ in range(40)]
x_values = [i for i in range(40)]
ax.axis([0, 9, 20, 40])
ax.plot(x_values, y_values)
ax_time = fig.add_axes([0.12, 0.1, 0.78, 0.03])
s_time = Slider(ax_time, 'Time', 0, 30, valinit=0)
def update(val):
pos = s_time.val
ax.axis([pos, pos+10, 20, 40])
fig.canvas.draw_idle()
s_time.on_changed(update)
Tk.mainloop()
Being new to python, I've come upon the matplotlib draw() freezes window problem myself and found the solution on this site:
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import random
import numpy as np
import sys
import Tkinter as tk
import time
def function1(fig, ax):
ax.cla()
color_grade_classes = ['#80FF00','#FFFF00','#FF8000', '#FF0000']
varsi = random.randint(1, 100)
for colors, rows in zip(color_grade_classes, [3,2,1,0] ):
indexs = np.arange(5)
heights = [varsi,varsi/2,varsi/3,0,0]
ax.bar(indexs, heights, zs = rows, zdir='y', color=colors, alpha=0.8)
return fig
class App():
def __init__(self):
self.root = tk.Tk()
self.root.wm_title("Embedding in TK")
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111, projection='3d')
self.ax.set_xlabel('X')
self.ax.set_ylabel('Y')
self.fig = function1(self.fig, self.ax)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
self.toolbar = NavigationToolbar2TkAgg( self.canvas, self.root )
self.toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
self.fig = function1(self.fig,self.ax)
self.canvas.show()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
app=App()
My problem is incorporating the following plotting code into it. It's not quite the same as the example given. Not sure how to split this up between the function definition and the class declaration. Can anyone help me on this?
t0 = time.time()
while time.time() - t0 <= 10:
data = np.random.random((32, 32))
plt.clf()
im = plt.imshow(data,cmap=cm.gist_gray, interpolation='none')
plt.ion()
cbar = plt.colorbar(im)
cbar.update_normal(im)
cbar.set_clim(0, np.amax(data))
plt.draw()
time.sleep(0.5)
plt.show(block=True)
this seems to work.
Basically the __init__ part initialises the plot and draws the first "frame". Then the function self.update_clockis called every 1000ms, and that function calls function1() which generates new data and redraws the plot.
I moved things around a bit because of the colorbar in your example, but the idea remains the same.
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import random
import numpy as np
import sys
import Tkinter as tk
import time
class App():
def __init__(self):
self.root = tk.Tk()
self.root.wm_title("Embedding in TK")
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111)
self.ax.set_xlabel('X')
self.ax.set_ylabel('Y')
data = np.random.random((32, 32))
im = self.ax.imshow(data,cmap=cm.gist_gray, interpolation='none')
self.cbar = self.fig.colorbar(im)
self.cbar.update_normal(im)
self.cbar.set_clim(0, np.amax(data))
self.fig = self.function1(self.fig, self.ax)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
self.toolbar = NavigationToolbar2TkAgg( self.canvas, self.root )
self.toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
self.fig = self.function1(self.fig,self.ax)
self.canvas.show()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
def function1(self, fig, ax):
ax.cla()
data = np.random.random((32, 32))
im = ax.imshow(data,cmap=cm.gist_gray, interpolation='none')
self.cbar.update_normal(im)
self.cbar.set_clim(0, np.amax(data))
return fig
app=App()