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()
Related
I am trying to plot a figure that has many lines where each line represents a specifc temperature!
An example of what I want is here:
However, I bulit the following code:
x=pd.DataFrame(df1, columns =[0])
J = set(x.iloc[:,0])
print ('Length Temperature',len(J))
O = len(J)
M = len(df1.index)
print('Indexxxxx: ',df1.iloc[0:12+0,5])
for i in range(0,M,O):
figure3 = plt.Figure(figsize=(8, 6), dpi=80)
ax1 = figure3.add_subplot(111)
ax1.scatter(df1.iloc[i+1:M+i,5],df1.iloc[i+1:M+i,6], label = "Temperature " + str((df1.iloc[i, 0])))
scatter1 = FigureCanvasTkAgg(figure3, GraphWindow)
scatter1.get_tk_widget().pack(side=tk.LEFT, fill=tk.BOTH)
ax1.set_xlabel('Reduced Frequency [Hz]')
ax1.set_ylabel('Complex Shear Modulus G*')
ax1.set_yscale('log')
ax1.set_xscale('log')
ax1.set_title('MasterCurve ')
ax1.set_facecolor('whitesmoke')
figure3.patch.set_facecolor('whitesmoke')
ax1.spines['bottom'].set_color('black')
ax1.spines['top'].set_color('black')
ax1.spines['left'].set_color('black')
ax1.spines['right'].set_color('black')
toobar = NavigationToolbar2Tk(scatter1, GraphWindow)
ax1.legend(['(Temperature)' + str((df1.iloc[i, 0]))])
hold(True)
Everything is fine in this code but I am obtaining the lines in blue and the legend is the same for all of them.. This is what I obtained:
My question is, how can I change the color of each line and add new legend in each iteration in the above for loop.
Thanks in advance!
You want a single Figure, a single Axes and a single Canvas, and plot the different curves inside of them. In other words, you do too much inside the cycle…
import tkinter
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import numpy as np
# frequencies and a poor man's dataframe
freq = np.logspace(-1.5, 1.5, 12)
G = {'ABCDE'[n-1]:np.logspace(n-4, n-2-n/4, 12) for n in range(1, 6)}
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
fig = Figure(figsize=(6, 4), dpi=100, layout='constrained')
ax = fig.add_subplot()
# this is our loop on the different curves
lines = [ax.plot(freq, G[letter], '-o', label=letter)[0] for letter in G]
# titles, log axes, legend
ax.set_xlabel('Reduced Frequency [Hz]')
ax.set_ylabel('Complex Shear Modulus G*')
ax.set_yscale('log')
ax.set_xscale('log')
ax.set_title('MasterCurve ')
ax.set_facecolor('whitesmoke')
ax.legend()
connect the figure to a tkinter Canvas --- once
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
# boilerplate
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
toolbar.update()
canvas.mpl_connect("key_press_event", key_press_handler)
button_quit = tkinter.Button(master=root, text="Quit", command=root.destroy)
button_quit.pack(side=tkinter.BOTTOM)
toolbar.pack(side=tkinter.BOTTOM, fill=tkinter.X)
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True)
# let's show our curves
tkinter.mainloop()
I want to create a Tkinter app in which the user can enter the data (just by choosing the Excel file) and can see the graphs by using Tkinter Check Buttons. So I decided to use Matplotlib for that, but it opens in another new window. It will be excellent if those charts open in the same window. (I also would prefer if that library would show the charts as Plotly Express)
Please upload a minimum reproducible example.
Without seeing that, you need to create a FigureCanvasTkAgg canvas to add your plots to if you haven't (docs).
import tkinter
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
import numpy as np
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
fig = Figure(figsize=(5, 4), dpi=100)
t = np.arange(0, 3, .01)
ax = fig.add_subplot()
line, = ax.plot(t, 2 * np.sin(2 * np.pi * t))
ax.set_xlabel("time [s]")
ax.set_ylabel("f(t)")
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
# pack_toolbar=False will make it easier to use a layout manager later on.
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
toolbar.update()
canvas.mpl_connect(
"key_press_event", lambda event: print(f"you pressed {event.key}"))
canvas.mpl_connect("key_press_event", key_press_handler)
button_quit = tkinter.Button(master=root, text="Quit", command=root.quit)
def update_frequency(new_val):
# retrieve frequency
f = float(new_val)
# update data
y = 2 * np.sin(2 * np.pi * f * t)
line.set_data(t, y)
# required to update canvas and attached toolbar!
canvas.draw()
slider_update = tkinter.Scale(root, from_=1, to=5, orient=tkinter.HORIZONTAL,
command=update_frequency, label="Frequency [Hz]")
# Packing order is important. Widgets are processed sequentially and if there
# is no space left, because the window is too small, they are not displayed.
# The canvas is rather flexible in its size, so we pack it last which makes
# sure the UI controls are displayed as long as possible.
button_quit.pack(side=tkinter.BOTTOM)
slider_update.pack(side=tkinter.BOTTOM)
toolbar.pack(side=tkinter.BOTTOM, fill=tkinter.X)
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
tkinter.mainloop()
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()
how do i delete data from a figure when it is used in tktinter via FigureCanvasTkAgg?
I have tried to clear the figure with cla, clf and to delete the canvas with .delete, .clear.
My code: (If you have any advise how i can improve my code let me now. This is my first project in coding)
import tkinter as tk
from tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#createplot
fig_CDF = Figure(figsize = (6, 5), facecolor = "white")
axis_CDF = fig_CDF.add_subplot(111)
canvas_CDF = FigureCanvasTkAgg(fig_CDF, master = window_main)
canvas_CDF._tkcanvas.pack(side = tk.TOP, fill = tk.BOTH, expand = 1)
canvas_CDF.get_tk_widget().place(x=400,y=50)
#plotdata
axis_CDF.plot(datax, datay, label = "data",marker=".", linestyle = "")
canvas_CDF.draw()
Thanks for your help!
In this code I added three buttons which shows three methods - all of them need canvas_CDF.draw() to redraw element in window.
first replaces data in plot and it removes points from chart but it still show axes.
second clears axis - so it remove data and set axis to 0..1
third clears figure - it removes even axis.
First version needs
plot = axis_CDF.plot(...)
to have access to data.
I use random only to create some data.
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import random
# --- functions ---
def replace_data():
datax = []
datay = []
#datax = range(10)
#datay = [x/10 for x in random.sample(range(10), 10)]
plot[0].set_data(datax, datay)
canvas_CDF.draw()
def clear_axis():
#axis_CDF.clear()
axis_CDF.cla()
canvas_CDF.draw()
def clear_figure():
fig_CDF.clear()
#fig_CDF.clf()
canvas_CDF.draw()
# --- main ---
window_main = tk.Tk()
datax = range(10)
datay = [x/10 for x in random.sample(range(10), 10)]
# create plot
fig_CDF = Figure(figsize=(6, 5), facecolor="white")
axis_CDF = fig_CDF.add_subplot(111)
canvas_CDF = FigureCanvasTkAgg(fig_CDF, master=window_main)
canvas_CDF._tkcanvas.pack(side='top', fill='both', expand=True)
canvas_CDF.get_tk_widget().pack()
# plot data
plot = axis_CDF.plot(datax, datay, label="data", marker=".", linestyle="")
canvas_CDF.draw()
# buttons
b = tk.Button(window_main, text='Replace data', command=replace_data)
b.pack()
b = tk.Button(window_main, text='Clear axis', command=clear_axis)
b.pack()
b = tk.Button(window_main, text='Clear figure', command=clear_figure)
b.pack()
window_main.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()