Updating canvas in Tkinter - python

Does anybody know how to modify the following code so that the graph is updated every time I change a goniometric function? The plotting is done in function plot(). There are some related threads on stackoverflow, but I was not able to apply them on my example code...
Many thx,
Macky
import numpy as np
import Tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class App:
def __init__(self, window):
self.window = window
# set up pi
pi = 3.141592654
# create the frame and pack to the object
frame = tk.Frame(window)
frame.pack()
def callback(func, *pargs):
print(func.get())
func.set(func.get())
self.plot()
self.window.update()
self.func = tk.StringVar()
self.func.set("sin") # default value
self.func.trace('w', lambda *pargs: callback(self.func, *pargs))
self.button = tk.OptionMenu(window, self.func, "sin", "cos", "tan")
self.button.pack(side = tk.TOP)
self.min_value = tk.Entry(window, justify = tk.RIGHT)
self.min_value.pack()
self.min_value.delete(0, tk.END)
self.min_value.insert(0, -pi)
self.max_value = tk.Entry(window, justify = tk.RIGHT)
self.max_value.pack()
self.max_value.delete(0, tk.END)
self.max_value.insert(0, pi)
self.button = tk.Button(frame, text = "QUIT", foreground = "red", command = frame.quit)
self.button.pack(side = tk.BOTTOM)
self.draw = tk.Button(frame, text = "DRAW", command = self.plot())
self.draw.pack(side = tk.LEFT)
def plot(self):
# generate numbers for the plot
x = np.array(np.arange(np.float64(self.min_value.get()), np.float64(self.max_value.get()), 0.001))
if self.func.get() == 'sin':
print('plotting sin()')
y = np.sin(x)
elif self.func.get() == 'cos':
print('plotting cos()')
y = np.cos(x)
else:
print('plotting tan()')
y = np.tan(x)
# create the plot
fig = Figure(figsize = (6, 6))
a = fig.add_subplot(1,1,1)
a.plot(x, y, color = 'blue')
a.set_title ("Goniometric Functions", fontsize = 12)
a.set_ylabel(self.func.get() + '(x)', fontsize = 8)
a.set_xlabel('x', fontsize = 8)
# canvas
canvas = FigureCanvasTkAgg(fig, master = self.window)
self.widget = canvas.get_tk_widget().pack()
print('here I should update canvas')
canvas.draw()
root = tk.Tk()
app = App(root)
root.mainloop()
root.destroy()

You should create empty plot on canvas and later only replace data in plot using a.set_xdata(), a.set_ydata()
import numpy as np
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class App:
def __init__(self, window):
self.window = window
# set up pi
pi = 3.141592654
# create the frame and pack to the object
frame = tk.Frame(window)
frame.pack()
def callback(func, *pargs):
print(func.get())
#func.set(func.get()) # <-- it has no sense
self.update_plot()
self.window.update()
self.func = tk.StringVar()
self.func.set("sin") # default value
self.func.trace('w', lambda *pargs: callback(self.func, *pargs))
self.button = tk.OptionMenu(window, self.func, "sin", "cos", "tan")
self.button.pack(side = tk.TOP)
self.min_value = tk.Entry(window, justify = tk.RIGHT)
self.min_value.pack()
self.min_value.delete(0, tk.END)
self.min_value.insert(0, -pi)
self.max_value = tk.Entry(window, justify=tk.RIGHT)
self.max_value.pack()
self.max_value.delete(0, tk.END)
self.max_value.insert(0, pi)
self.button = tk.Button(frame, text="QUIT", foreground="red", command=root.destroy) #<--
self.button.pack(side = tk.BOTTOM)
self.draw = tk.Button(frame, text="DRAW", command=self.update_plot)
self.draw.pack(side = tk.LEFT)
# create empty plot
self.create_plot()
# update plot with current function at start
self.update_plot()
def create_plot(self):
# create the plot
self.fig = Figure(figsize = (6, 6))
self.a = self.fig.add_subplot(1,1,1)
self.p, _ = self.a.plot([], [], color = 'blue')
self.a.set_title ("Goniometric Functions", fontsize = 12)
# canvas
self.canvas = FigureCanvasTkAgg(self.fig, master = self.window)
self.widget = self.canvas.get_tk_widget().pack()
#self.canvas.draw()
def update_plot(self):
print('update')
# generate numbers for the plot
x = np.array(np.arange(np.float64(self.min_value.get()), np.float64(self.max_value.get()), 0.001))
if self.func.get() == 'sin':
print('plotting sin()')
y = np.sin(x)
elif self.func.get() == 'cos':
print('plotting cos()')
y = np.cos(x)
else:
print('plotting tan()')
y = np.tan(x)
# replace labels
self.a.set_ylabel(self.func.get() + '(x)', fontsize = 8)
self.a.set_xlabel('x', fontsize = 8)
# replace data
self.p.set_xdata(x)
self.p.set_ydata(y)
# rescale
self.a.relim()
self.a.autoscale_view()
# update screen
self.canvas.draw()
root = tk.Tk()
app = App(root)
root.mainloop()
#root.destroy() You don't need it

#furas - Thx a lot for the post. Figured out based on your comment 10 minutes after your post. Although being second to you, I am sending my version of the code as well ;). I think, we can close this thread as solved...
Macky
import numpy as np
import Tkinter as tk
import matplotlib.figure as fg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class App:
def __init__(self, window):
self.window = window
# set up pi
pi = 3.141592654
# create empty canvas
x = []
y = []
fig = fg.Figure(figsize = (4,4))
fig_subplot = fig.add_subplot(111)
self.line, = fig_subplot.plot(x, y)
self.canvas = FigureCanvasTkAgg(fig, master = self.window)
self.canvas.show()
self.canvas.get_tk_widget().pack(side = tk.TOP, fill = tk.BOTH, expand = 1)
# create the frame and pack to the object
frame = tk.Frame(window)
frame.pack()
def callback(func, *pargs):
print(func.get())
func.set(func.get())
self.plot()
self.func = tk.StringVar()
self.func.set("sin") # default value
self.func.trace('w', lambda *pargs: callback(self.func, *pargs))
self.button = tk.OptionMenu(window, self.func, "sin", "cos", "tan")
self.button.pack(side = tk.TOP)
self.min_value = tk.Entry(window, justify = tk.RIGHT)
self.min_value.pack()
self.min_value.delete(0, tk.END)
self.min_value.insert(0, -pi)
self.max_value = tk.Entry(window, justify = tk.RIGHT)
self.max_value.pack()
self.max_value.delete(0, tk.END)
self.max_value.insert(0, pi)
self.button = tk.Button(frame, text = "QUIT", foreground = "red", command = frame.quit)
self.button.pack(side = tk.BOTTOM)
def plot(self):
# generate numbers for the plot
x = np.array(np.arange(np.float64(self.min_value.get()), np.float64(self.max_value.get()), 0.001))
if self.func.get() == 'sin':
print('plotting sin()')
y = np.sin(x)
elif self.func.get() == 'cos':
print('plotting cos()')
y = np.cos(x)
else:
print('plotting tan()')
y = np.tan(x)
# update canvas
self.line.set_data(x, y)
ax = self.canvas.figure.axes[0]
ax.set_xlim(x.min(), x.max())
ax.set_ylim(y.min(), y.max())
self.canvas.draw()
root = tk.Tk()
app = App(root)
root.mainloop()
root.destroy()

Related

Showing matplotlib plots from function in tkinter

I have a problem regarding showing pyplots in tkinkter. I have a function plot_function that returns 4 matplotlib.pyplot-plots. I want to be able to show these plots in a tkinter GUI, and show one plot in the beginning and display the other three with two "next" and "previous" buttons. The plot_funtion is only run once.
At the moment I'm trying to do the following (simplified):
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class GUI:
def plot_function(self):
...
# Generating plots #
...
self.plots = [plt1, plt2, plt3, plt4]
self.init_plots()
def init_plots(self): # Showing the inital plot, plt1
self.plot_ctr = 0
fig = plt.Figure(figsize=(5,4), dpi = 80)
plot1 = fig.add_subplot(111)
plot1.show(self.plots[0])
first_fig = FigureCanvasTkAgg(fig,self.frame1)
first_fig.draw()
first_fig.get_tk_widget().pack(side=tk.LEFT, fill=tk.BOTH)
self.master.update_idletasks()
def next_plot(self):
if self.plot_ctr < 3:
self.plot_ctr += 1
else:
self.plot_ctr = 0
self.first_fig.config(image = self.plots[self.plots_ctr])
self.master.update_idletasks()
def prev_plot(self):
if self.plot_ctr > 0:
self.plot_ctr -= 1
else:
self.plot_ctr = 3
self.first_fig.config(image = self.plots[self.plots_ctr])
self.master.update_idletasks()
def __init__(self, master):
self.master = master
self.frame1 = tk.Frame(self.master, relief = tk.RAISED, highlightbackground = "black", highlightthickness = 1)
self.frame1.pack(side = tk.TOP, fill=tk.BOTH, expand=True)
self.next_btn = tk.Button(self.frame1, text = "Next plot", width = 10, command = self.next_plot)
self.prev_btn = tk.Button(self.frame1, text = "Previous plot", width = 10, command = self.prev_plot)
self.prev_btn.pack(side = tk.LEFT, padx = 10, pady = 10)
self.next_btn.pack(side = tk.LEFT, padx = 10, pady = 10)
root = tk.Tk()
my_gui = GUI(root)
root.mainloop()
I feel very unsure on how to properly use TkAgg and would be very grateful on any tips on how I would get this to work.
Here is an example showing how you could organize your code to rotate a sequence of matplotlib figures embedded in a tkinter window
import matplotlib
import tkinter as tk
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
class Data:
def __init__(self):
self.x = list(range(10))
self.y0 = self.x[:]
self.y1 = [5 for _ in range(10)]
self.y2 = [-y for y in self.y0]
self.d = [self.y0, self.y1, self.y2]
def get_next_dataset(self):
y = self.d.pop(0)
self.d.append(y)
return self.x, y
class MPLGraph(Figure):
def __init__(self, data):
Figure.__init__(self, figsize=(5, 5), dpi=100)
self.data = data
def obtain_next_figure(self):
self.plot = self.add_subplot(111)
self.plot.plot(*self.data.get_next_dataset())
return self
class GraphPage(tk.Frame):
def __init__(self, parent, graphs):
tk.Frame.__init__(self, parent)
self.title_label = tk.Label(self, text="Graph Page Example")
self.title_label.pack()
self.pack()
self.next_graph_btn = tk.Button(self, text='next graph', command=self.obtain_next_figure)
self.next_graph_btn.pack()
self.graphs = graphs
self.obtain_next_figure()
def obtain_next_figure(self):
try: # protects against AttributeError the first time when self.figure_widget not yet exists yet
self.figure_widget.destroy()
except AttributeError:
pass
self.add_mpl_figure(self.graphs.obtain_next_figure())
def add_mpl_figure(self, fig):
self.mpl_canvas = FigureCanvasTkAgg(fig, self)
self.figure_widget = self.mpl_canvas.get_tk_widget()
self.figure_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
if __name__ == '__main__':
root = tk.Tk()
graph_page = GraphPage(root, MPLGraph(Data()))
root.mainloop()

Updating the color of embeded matplotlib plot in tkinter

I tried to put some option for embedded matplotlib plot in tkinter. The style of plot (i.e. solid line or dashed line) changes properly but the color does not. However, by resizing the tkinter window the color got updated. The related part of my code is demonstrated below:
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from tkinter.colorchooser import askcolor
import numpy as np
class Ploter:
DEFAULT_PEN_SIZE = 5.0
DEFAULT_COLOR = 'black'
DEFAULT_STYLE = '-'
def __init__(self, master, size = (5,4), dpi = 100):
self.master = master
self.size = size
self.dpi = dpi
self.color = self.DEFAULT_COLOR
self.style = self.DEFAULT_STYLE
self.fig = Figure(figsize = self.size, dpi=self.dpi)
self.canvas = FigureCanvasTkAgg(self.fig , master=self.master)
self.ax = self.fig.add_subplot(111)
self.t = np.arange(0, 3, 0.01)
self.tf = 2 * np.sin(2 * np.pi * self.t)
def PlotWidgets(self):
toolbar = NavigationToolbar2TkAgg(self.canvas, self.master)
toolbar.update()
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
list1 = ["Solid", "Dashed", "Dash-dot", 'Dotted']
self.line_style = tk.StringVar()
self.line_style.set('Line Style')
self.line_style.trace("w", self.choose_style)
droplist = tk.OptionMenu(self.master, self.line_style, *list1)
droplist.config(width=10)
droplist.pack(side = tk.LEFT , padx = 10 , pady = 10)
color_button = tk.Button(master = self.master, text='color',
command = self.choose_color)
color_button.pack(side = tk.LEFT , padx = 10 , pady = 10)
self.ploter()
def ploter(self):
self.ax.clear()
style = self.style
self.ax.plot(self.t, self.tf, style , color = self.color)
self.canvas.draw()
def choose_color(self):
style = self.style
self.color = askcolor(color=self.color)[1]
self.ax.plot(self.t, self.tf, style , color = self.color)
#self.canvas.draw()
def choose_style(self, * args):
if self.line_style.get() == "Solid":
self.style = '-'
self.ploter()
if self.line_style.get() == "Dashed":
self.style = '--'
self.ploter()
if self.line_style.get() == "Dash-dot":
self.style = '-.'
self.ploter()
if self.line_style.get() == "Dotted":
self.style = ':'
self.ploter()
root = tk.Tk()
root.wm_title("Embedding in tkinter")
pp = Ploter(root)
pp.PlotWidgets()
root.mainloop()
I have tried rebooting the computer, different IDE or running the script from terminal, and none of them remedy the situation.
It is worth to mention that I am using Linux mint 19.2 (MATE desktop, x11 display server), python 3.6.8, matplotlib 2.1.1, tkinter 8.6.
Add canvas.draw_idle() to both your ploter and choose_color func:
def ploter(self):
self.ax.clear()
style = self.style
self.ax.plot(self.t, self.tf, style , color = self.color)
self.canvas.draw_idle()
def choose_color(self):
style = self.style
self.color = askcolor(color=self.color)[1]
self.ax.plot(self.t, self.tf, style , color = self.color)
self.canvas.draw_idle()

Python Tkinter, Display Live Data

I want to display live data in a GUI, in tkinter. The data I am getting contains a list of two integers [current, voltage]. I am getting new data every second.
I managed to create a GUI, now I want to know how to display data in GUI Label widgets (python tkinter) and update labels dynamically. Any suggestions please
Here is my code so far:
#data getting is a list eg. [10, 12]
from tkinter import *
import tkinter.font
#main Window using Tk
win = Tk()
win.title("v1.0")
win.geometry('800x480')
win.configure(background='#CD5C5C')
#Labels
voltage = Label(win, text = "voltage")
voltage.place(x=15, y=100)
current = Label(win, text = "current")
current.place(x=15, y=200)
#display measured values
#how to display here !!!
currentValues = Label(win, text = "want to display somewhere like this")
currentValues.place(x=200, y=100)
voltageValues = Label(win, text = "want to display somewhere like this")
voltageValues.place(x=200, y=200)
mainloop()
If you want to graph your live data and want to avoid using other libraries to do that for you, you might find the following to be an enlightening starting point for creating your own graphs. The sample draws a full circle of values when evaluating the math.sin function that comes in the standard library. The code takes into account automatic sampling, resizing, and updating as needed and should be fairly responsive.
#! /usr/bin/env python3
import math
import threading
import time
import tkinter.ttk
import uuid
from tkinter.constants import EW, NSEW, SE
class Application(tkinter.ttk.Frame):
FPS = 10 # frames per second used to update the graph
MARGINS = 10, 10, 10, 10 # internal spacing around the graph
#classmethod
def main(cls):
tkinter.NoDefaultRoot()
root = tkinter.Tk()
root.title('Tkinter Graphing')
# noinspection SpellCheckingInspection
root.minsize(640, 480) # VGA (NTSC)
cls(root).grid(sticky=NSEW)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
def __init__(self, master=None, **kw):
super().__init__(master, **kw)
self.display = tkinter.Canvas(self, background='white')
self.display.bind('<Configure>', self.draw)
self.start = StatefulButton(self, 'Start Graphing', self.start_graph)
self.grip = tkinter.ttk.Sizegrip(self)
self.grid_widgets(padx=5, pady=5)
self.data_source = DataSource()
self.after_idle(self.update_graph, round(1000 / self.FPS))
self.run_graph = None
def grid_widgets(self, **kw):
self.display.grid(row=0, column=0, columnspan=2, sticky=NSEW, **kw)
self.start.grid(row=1, column=0, sticky=EW, **kw)
self.grip.grid(row=1, column=1, sticky=SE)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def start_graph(self):
self.run_graph = True
threading.Thread(target=self.__simulate, daemon=True).start()
return 'Stop Graphing', self.stop_graph
def stop_graph(self):
self.run_graph = False
return 'Clear Graph', self.clear_graph
def clear_graph(self):
self.data_source.clear()
self.reset_display()
return 'Start Graphing', self.start_graph
# def __simulate(self):
# # simulate changing populations
# for population in itertools.count():
# if not self.run_graph:
# break
# self.data_source.append(population, get_max_age(population, 200))
# def __simulate(self):
# # simulate changing ages
# for age in itertools.count(1):
# if not self.run_graph:
# break
# self.data_source.append(age, get_max_age(250_000_000, age))
def __simulate(self):
# draw a sine curve
for x in range(800):
time.sleep(0.01)
if not self.run_graph:
break
self.data_source.append(x, math.sin(x * math.pi / 400))
def update_graph(self, rate, previous_version=None):
if previous_version is None:
self.reset_display()
current_version = self.data_source.version
if current_version != previous_version:
data_source = self.data_source.copy()
self.draw(data_source)
self.after(rate, self.update_graph, rate, current_version)
def reset_display(self):
self.display.delete('data')
self.display.create_line((0, 0, 0, 0), tag='data', fill='black')
def draw(self, data_source):
if not isinstance(data_source, DataSource):
data_source = self.data_source.copy()
if data_source:
self.display.coords('data', *data_source.frame(
self.MARGINS,
self.display.winfo_width(),
self.display.winfo_height(),
True
))
class StatefulButton(tkinter.ttk.Button):
def __init__(self, master, text, command, **kw):
kw.update(text=text, command=self.__do_command)
super().__init__(master, **kw)
self.__command = command
def __do_command(self):
self['text'], self.__command = self.__command()
def new(obj):
kind = type(obj)
return kind.__new__(kind)
def interpolate(x, y, z):
return x * (1 - z) + y * z
def interpolate_array(array, z):
if z <= 0:
return array[0]
if z >= 1:
return array[-1]
share = 1 / (len(array) - 1)
index = int(z / share)
x, y = array[index:index + 2]
return interpolate(x, y, z % share / share)
def sample(array, count):
scale = count - 1
return tuple(interpolate_array(array, z / scale) for z in range(count))
class DataSource:
EMPTY = uuid.uuid4()
def __init__(self):
self.__x = []
self.__y = []
self.__version = self.EMPTY
self.__mutex = threading.Lock()
#property
def version(self):
return self.__version
def copy(self):
instance = new(self)
with self.__mutex:
instance.__x = self.__x.copy()
instance.__y = self.__y.copy()
instance.__version = self.__version
instance.__mutex = threading.Lock()
return instance
def __bool__(self):
return bool(self.__x or self.__y)
def frame(self, margins, width, height, auto_sample=False, timing=False):
if timing:
start = time.perf_counter()
x1, y1, x2, y2 = margins
drawing_width = width - x1 - x2
drawing_height = height - y1 - y2
with self.__mutex:
x_tuple = tuple(self.__x)
y_tuple = tuple(self.__y)
if auto_sample and len(x_tuple) > drawing_width:
x_tuple = sample(x_tuple, drawing_width)
y_tuple = sample(y_tuple, drawing_width)
max_y = max(y_tuple)
x_scaling_factor = max(x_tuple) - min(x_tuple)
y_scaling_factor = max_y - min(y_tuple)
coords = tuple(
coord
for x, y in zip(x_tuple, y_tuple)
for coord in (
round(x1 + drawing_width * x / x_scaling_factor),
round(y1 + drawing_height * (max_y - y) / y_scaling_factor)))
if timing:
# noinspection PyUnboundLocalVariable
print(f'len = {len(coords) >> 1}; '
f'sec = {time.perf_counter() - start:.6f}')
return coords
def append(self, x, y):
with self.__mutex:
self.__x.append(x)
self.__y.append(y)
self.__version = uuid.uuid4()
def clear(self):
with self.__mutex:
self.__x.clear()
self.__y.clear()
self.__version = self.EMPTY
def extend(self, iterable):
with self.__mutex:
for x, y in iterable:
self.__x.append(x)
self.__y.append(y)
self.__version = uuid.uuid4()
if __name__ == '__main__':
Application.main()
You can change label text dynamically:
This is a way using textvariable option with StringVar and .set() method
str_var = tk.StringVar(value="Default")
currentValues= Label(win, textvariable=my_string_var)
currentValues.place(x=200, y=100)
str_var.set("New value")
Another way using simply .configure() method
currentValues = Label(win, text = "default")
currentValues.configure(text="New value")
Finally, to make the UI update without waiting the rest of the loop do an update
win.update()
I figured out a liveplot inspired by a demo, where I used this to plot the realtime current-voltage scan in Keithley2410.
The whole script is below:
import matplotlib.pyplot as plt
import numpy as np
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from tkinter import ttk
x_data, y_data = [], []
class Win(tk.Tk):
def __init__(self):
super().__init__()
self.title('I-V liveplot')
self.geometry('500x450')
# Frame that holds wigets on the left side
left_frame = ttk.Frame(self)
left_frame.pack(side= "left", padx =10, pady = 10) #, fill="y", expand=True
self.fig = plt.figure(figsize=(4, 3.5), dpi=100)
self.ax = self.fig.add_subplot(1,1,1)
self.line, = self.ax.plot([0], [0])
self.ax.set_xlabel('Voltage / V', fontsize = 12)
self.ax.set_ylabel('Current / A', fontsize = 12)
self.fig.tight_layout()
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.toolbar = NavigationToolbar2Tk(self.canvas, self)
self.canvas.get_tk_widget().pack(side= tk.BOTTOM)
voltage_range_label = tk.Label(left_frame, text = "Voltage range")
voltage_range_label.pack(side = "top", padx =10, pady =2)
self.voltage_range = tk.IntVar()
self.voltage_range.set(10)
voltage_range_spinbox = ttk.Spinbox(left_frame, from_=-3e2, to = 5e2, textvariable = self.voltage_range, width=8)
voltage_range_spinbox.pack(side="top", padx =10, pady =5)
voltage_step_label = tk.Label(left_frame, text = "Step")
voltage_step_label.pack(side = "top", padx =10, pady =2)
self.step = tk.IntVar()
self.step.set(1)
step_spinbox = ttk.Spinbox(left_frame, from_=-3e2, to = 5e2, textvariable = self.step, width =9)
step_spinbox.pack(side="top", padx =10, pady =5)
self.start = tk.BooleanVar(value = False)
start_butt = ttk.Button(left_frame, text="Start", command= lambda: self.start.set(True))
start_butt.pack(side='top', padx =10, pady =10)
stop_butt = ttk.Button(left_frame, text="Resume", command=lambda: self.is_paused.set(False))
stop_butt.pack(side="top", padx =10, pady =10)
self.is_paused = tk.BooleanVar() # variable to hold the pause/resume state
restart_butt = ttk.Button(left_frame, text="Pause", command=lambda: self.is_paused.set(True))
restart_butt.pack(side="top", padx =10, pady =10)
def update(self, k=1):
if self.start.get() and not self.is_paused.get():
# quasi For Loop
idx = [i for i in range(0, k, self.step.get())][-1]
x_data.append(idx)
y_data.append(np.sin(idx/5))
self.line.set_data(x_data, y_data)
self.fig.gca().relim()
self.fig.gca().autoscale_view()
self.canvas.draw()
#self.canvas.flush_events()
k += self.step.get()[![enter image description here][2]][2]
if k <= self.voltage_range.get():
self.after(1000, self.update, k)
if __name__ == "__main__":
app = Win()
app.after(1000, app.update)
app.mainloop()
This code works properly and results in an output shown in Graph. I hope it would be helpful.
.
I want to display some live data in a GUI.
I think what you want to do is use the .after() method. The .after() method queues tkinter to run some code after a set time.
For example:
currentValues = Label(win, text = "want to display somewhere like this")
currentValues.place(x=200, y=100)
voltageValues = Label(win, text = "want to display somewhere like this")
voltageValues.place(x=200, y=200)
def live_update():
currentValues['text'] = updated_value
voltageValues['text'] = updated_value
win.after(1000, live_update) # 1000 is equivalent to 1 second (closest you'll get)
live_update() # to start the update loop
1000 units in the after method is the closest you'll get to 1 second exactly.

Error in matplotlib popup window (AttributeError: 'NoneType' object has no attribute 'set_canvas')

I tried to plot the graph in pop up window. It pops up. But there is an error.
import tkinter as tk
window = tk.Tk()
window.configure(background='white')
label_1 = tk.Label(window, text="Conpyright 123456789123456798", anchor=tk.S)
label_1.pack()
ws = window.winfo_screenwidth()
hs = window.winfo_screenheight()
w = 980 # width for the Tk root
h = 600 # height for the Tk root
x = (ws / 2) - (w / 2)
y = (hs / 2) - (h / 2)
window.geometry('%dx%d+%d+%d' % (w, h, x, y))
canvas = tk.Canvas(window, bg="white", width=980, height=580, highlightthickness=0)
canvas.pack()
canvas_scroll = tk.Scrollbar(canvas, command=canvas.yview)
canvas_scroll.place(relx=1, rely=0, relheight=1, anchor=tk.NE)
canvas.configure(yscrollcommand=canvas_scroll.set, scrollregion=())
minw_var = tk.DoubleVar()
entry_minw_number = tk.Entry(canvas, textvariable=minw_var)
canvas.create_window(220,215, window=entry_minw_number)
maxw_var = tk.DoubleVar()
entry_maxw_number = tk.Entry(canvas, textvariable=maxw_var)
canvas.create_window(355,215, window=entry_maxw_number)
minl_var = tk.DoubleVar()
entry_minl_number = tk.Entry(canvas, textvariable=minl_var)
canvas.create_window(220,240, window=entry_minl_number)
maxl_var = tk.DoubleVar()
entry_maxl_number = tk.Entry(canvas, textvariable=maxl_var)
canvas.create_window(355,240, window=entry_maxl_number)
rect_var = tk.IntVar()
entry_rect_number = tk.Entry(canvas, textvariable=rect_var)
canvas.create_window(290,270, window=entry_rect_number)
And this is the part for matplotlib
-------------------------------------------------------------------------
def plot_sheet(self):
fig,ax = plt.subplots(1)
ax.set_xlim([0, self.W])
ax.set_ylim([0, self.L])
recs = []
for i in range(len(self.rect_list)):
if self.rect_rotate[i]:
ax.add_patch(patches.Rectangle((self.rect_pos[i][0], self.rect_pos[i][1]), self.rect_list[i].l, self.rect_list[i].w,linewidth=3,edgecolor='r'))
else:
ax.add_patch(patches.Rectangle((self.rect_pos[i][0], self.rect_pos[i][1]), self.rect_list[i].w, self.rect_list[i].l,linewidth=3,edgecolor='r'))
#plt.show()
return fig
def plot_sheets(self):
for i in range(len(self.sheets)):
self.sheets[i].plot_sheet()
def cal_culate1():
fig = packing_options[best_index].plot_sheets()
dataPlot = FigureCanvasTkAgg(fig, master = window)
dataPlot.show()
dataPlot.get_tk_widget().pack(side='top', fill='both', expand=1)
window.mainloop()
I wrote dataPlot = FigureCanvasTkAgg(fig, master = window). There is an error in master = window.
File "", line 687, in cal_culate1
dataPlot = FigureCanvasTkAgg(fig, master = window)
File "C:\Users\sel\Anaconda3\lib\site-packages\matplotlib\backends_backend_tk.py", line 204, in init
super(FigureCanvasTk, self).init(figure)
File "C:\Users\sel\Anaconda3\lib\site-packages\matplotlib\backend_bases.py", line 1618, in init
figure.set_canvas(self)
AttributeError: 'NoneType' object has no attribute 'set_canvas'
What should be written there?
You didn't show how you create your class for plotting, so i can only go by assumption here. First create a empty list:
import tkinter as tk
window = tk.Tk()
window.configure(background='white')
figure_holder = []
Then append to the list when you create your figure:
def plot_sheets(self):
for i in range(len(self.sheets)):
a = self.sheets[i].plot_sheet()
figure_holder.append(a)
Retrieve the figure object from the list when you plot it:
def cal_culate1():
fig = figure_holder[0]
dataPlot = FigureCanvasTkAgg(fig, master = window)
#dataPlot.show()
dataPlot.get_tk_widget().pack(side='top', fill='both', expand=1)
I made minimal working example which shows how do this.
it will need changes for your code but I don't know what you have in code and you didn't create minimal working example.
It creates three figures in generate_all_figures (in your code it will be plot_sheets with s) using plot_sheet (without s) and keep on list.
window display first figure from this list.
Buttons remove canvas with figure and create new canvas with next/previous figure from list.
I use grid() instead of pack() because this way I can easily put new canvas in the same place.
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class MyClass():
def __init__(self):
self.sheets = [[1,2,3], [3,1,2], [1,5,1]]
self.W = 2
self.L = 5
self.all_figures = []
def plot_sheet(self, data):
"""plot single figure"""
fig, ax = plt.subplots(1)
ax.set_xlim([0, self.W])
ax.set_ylim([0, self.L])
ax.plot(data)
return fig
def generate_all_figures(self):
"""create all figures and keep them on list"""
for data in self.sheets:
fig = self.plot_sheet(data)
self.all_figures.append(fig)
def show_figure(number):
global dataPlot
# remove old canvas
if dataPlot is not None: # at start there is no canvas to destroy
dataPlot.get_tk_widget().destroy()
# get figure from list
one_figure = my_class.all_figures[number]
# display canvas with figuere
dataPlot = FigureCanvasTkAgg(one_figure, master=window)
dataPlot.draw()
dataPlot.get_tk_widget().grid(row=0, column=0)
def on_prev():
global selected_figure
# get number of previous figure
selected_figure -= 1
if selected_figure < 0:
selected_figure = len(my_class.all_figures)-1
show_figure(selected_figure)
def on_next():
global selected_figure
# get number of next figure
selected_figure += 1
if selected_figure > len(my_class.all_figures)-1:
selected_figure = 0
show_figure(selected_figure)
# --- main ---
my_class = MyClass()
my_class.generate_all_figures()
window = tk.Tk()
window.rowconfigure(0, minsize=500) # minimal height
window.columnconfigure(0, minsize=700) # minimal width
# display first figure
selected_figure = 0
dataPlot = None # default value for `show_figure`
show_figure(selected_figure)
# add buttons to change figures
frame = tk.Frame(window)
frame.grid(row=1, column=0)
b1 = tk.Button(frame, text="<<", command=on_prev)
b1.grid(row=0, column=0)
b2 = tk.Button(frame, text=">>", command=on_next)
b2.grid(row=0, column=1)
window.mainloop()
Probably it could be done without replacing canvas but by replacing data in plot (fig.data ???, ax.data ??? I don't remember)

Tkinter Label not being updated with textvariable

In my code I see that the Label is not being updated with the 'textvariable', despite I believe I'm doing it right (probably not!).
varmuTemperature = StringVar(value="default value")
self.Label = Label(Frame2, textvariable = varmuTemperature)
self.Label.pack()
This should show a label with "default value" written on it. The problem is that I don't see anything written.
I have my code posted here.
import matplotlib
import matplotlib.artist as artists
import matplotlib.pyplot as plt
#import matplotlib.mlab as mlab
import scipy.stats
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,
NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
import numpy as np
import statistics
from tkinter import *
from tkinter import ttk
import serial
import time
import itertools
integer=0
xList = []
humidityList = []
humidityListHistogram = []
temperatureList = []
temperatureListHistogram = []
cnt=0
if sys.platform.startswith('win'):
ports = ['COM%s' % (i + 1) for i in range(256)]
elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
# this excludes your current terminal "/dev/tty"
ports = glob.glob('/dev/tty[A-Za-z]*')
elif sys.platform.startswith('darwin'):
ports = glob.glob('/dev/tty.*')
else:
raise EnvironmentError('Unsupported platform')
ser = serial.Serial()
style.use("seaborn-whitegrid")
#varmuTemperature = StringVar()
#varmuHumidity = StringVar()
f=plt.figure(0, figsize=(20,10))
humidityGraph = plt.subplot(224)
humidityGraph.set_title("Humidity vs Time")
humidityGraph.set_ylabel("Humidity RAW (Dec)")
humidityGraph.set_xlabel("Sample ()")
temperatureGraph = plt.subplot(223)
temperatureGraph.set_title("Temperature vs Time")
temperatureGraph.set_ylabel("Temperature RAW (Dec)")
temperatureGraph.set_xlabel("Sample ()")
humidityGraphHistogram = plt.subplot(222)
temperatureGraphHistogram = plt.subplot(221)
temperatureGraphHistogramNormal = temperatureGraphHistogram.twinx()
humidityGraphHistogramNormal = humidityGraphHistogram.twinx()
side_text = plt.figtext(0.93, 0.5, 'Text 1'+'\n'+'Text 2', bbox=dict(facecolor='white'))
plt.subplots_adjust(left = 0.05, right = 0.95, bottom = 0.05, top = 0.95, wspace = 0.16, hspace = 0.21)
class make_window():
def __init__(self, *args, **kwargs):
win = Tk()
win.title("Test")
win.state("zoomed")
Frame1 = Frame(win)
Frame1.pack()
self.comboBoxAvailableCOMPort = ttk.Combobox(Frame1, width = 30)
self.comboBoxAvailableCOMPort['values'] = []
self.comboBoxAvailableCOMPort.pack(padx=5, pady=5, side = LEFT)
self.buttonCheckComAvailable = Button(Frame1, text="Check COM Available", command = self.CheckComAvailable)
self.buttonCheckComAvailable.pack(padx=5, pady=10, side = LEFT)
self.buttonOpenCOMPort = Button(Frame1, text="Open COM Port", command = self.OnOpenCom)
self.buttonOpenCOMPort.pack(padx=5, pady=10, side = LEFT)
self.buttonCloseCOMPort = Button(Frame1, text="Close COM Port" , command = self.OnCloseCOM)
self.buttonCloseCOMPort.pack(padx=5, pady=10,side = LEFT)
self.CheckComAvailable()
Frame2 = Frame(win, highlightbackground = "red", highlightcolor = "red", highlightthickness = 1)
Frame2.pack()
varmuTemperature = StringVar(value="default value")
varmuTemperature.set("trerta")
print(varmuTemperature.get())
self.Label = Label(Frame2, textvariable = varmuTemperature)
self.Label.pack()
self.buttonCloseProgram = Button(Frame2, text="Close Program", command = self.OnCloseProgram)
self.buttonCloseProgram.pack(expand=True, fill='x', anchor='s')
Frame3 = Frame(win)
Frame3.pack()
canvas = FigureCanvasTkAgg(f, Frame3)
canvas.get_tk_widget().pack(padx=5, pady=10, side=BOTTOM, expand = True)
toolbar = NavigationToolbar2Tk(canvas, Frame3)
toolbar.update()
canvas._tkcanvas.pack(padx=5, pady=10,side = TOP)
def CheckComAvailable(self):
self.comboBoxAvailableCOMPort['values'] =[]
result = []
for port in ports:
try:
s = serial.Serial(port)
s.close()
result.append(port)
except (OSError, serial.SerialException):
pass
self.comboBoxAvailableCOMPort['values'] += tuple(result)
self.comboBoxAvailableCOMPort.set(result[0])
def OnOpenCom(self):
ser.baudrate = 115200
ser.port = self.comboBoxAvailableCOMPort.get()
try:
ser.open()
ser.readline()
ser.write("#dut,0$\n".encode('utf-8'))
ser.readline()
ser.write("#v,1800,1800$\n".encode('utf-8'))
ser.write("#W,77,08,07$\n".encode('utf-8'))
ser.readline()
except(OSError):
print("COM Port in use")
def OnCloseCOM(self):
global xList
global humidityList
global temperatureList
global humidityListHistogram
global temperatureListHistogram
global integer
integer=0
xList = []
humidityList = []
temperatureList = []
ser.close()
def OnCloseProgram(self):
self.OnCloseCOM()
exit()
## def toggle_geom(self,event):
## geom=self.master.winfo_geometry()
## print(geom,self._geom)
## self.master.geometry(self._geom)
## self._geom=geom
def animate(i):
global integer
global cnt
try:
ser.write(("#R,77,00,03$" + chr(10)).encode('utf-8'))
humidityLine=ser.readline()
inthumidityLine= int(humidityLine,16)
if (inthumidityLine > 8388608):
inthumidityLine = inthumidityLine - 16777216
humidityList.append(inthumidityLine)
humidityListHistogram.append(inthumidityLine)
ser.write(("#R,77,03,03$" + chr(10)).encode('utf-8'))
temperatureLine=ser.readline()
LineHistogram = temperatureLine
inttemperatureLine= int(temperatureLine,16)
if (inttemperatureLine > 8388608):
inttemperatureLine = inttemperatureLine - 16777216
temperatureList.append(inttemperatureLine)
temperatureListHistogram.append(inttemperatureLine)
xList.append(integer)
integer+=1
##################################################################################################################
## Creates the HUMIDITY Graphics
##################################################################################################################
humidityGraph.clear()
humidityGraph.plot(xList,humidityList,'-b*', label = "Humidity RAW")
humidityGraph.legend(loc='upper right', fancybox = True, frameon = True, shadow = True)
humidityGraph.set_title("Humidity vs Time")
humidityGraph.set_ylabel("Humidity RAW (Dec)")
humidityGraph.set_xlabel("Sample ()")
muHumidity = statistics.mean(humidityListHistogram)
#print("Mean = " + str(muHumidity) + " ; N = " + str(len(humidityListHistogram)))
#global varmuHumidity
#varmuHumidity.set("Humidity: ")
if (len(humidityListHistogram) > 1):
sigmaHumidity = statistics.pstdev(humidityListHistogram)
else:
sigmaHumidity = 100
humidityGraphHistogram.clear()
nHumidity, binsHumidity, patchesHumidity = humidityGraphHistogram.hist(humidityListHistogram, 100, density=False, facecolor='blue', alpha=0.75, histtype = 'stepfilled')
normalDistHumidity = scipy.stats.norm.pdf(binsHumidity, muHumidity, sigmaHumidity)
humidityGraphHistogramNormal.clear()
humidityGraphHistogramNormal.plot(binsHumidity, normalDistHumidity, 'r--')
humidityGraphHistogram.set_title("Histogram for Humidity Data")
humidityGraphHistogram.set_ylabel("Humidity RAW Counts (Dec)")
humidityGraphHistogram.set_xlabel("BINS ()")
humidityGraphHistogramNormal.set_ylabel("Normal Distribution")
##################################################################################################################
## Creates the TEMPERATURE Graphics
##################################################################################################################
temperatureGraph.clear()
temperatureGraph.plot(xList,temperatureList,'-r*', label = "Temperature RAW")
temperatureGraph.legend(loc='upper right', fancybox = True, frameon = True, shadow = True)
temperatureGraph.set_title("Temperature vs Time")
temperatureGraph.set_ylabel("Temperature RAW (Dec)")
temperatureGraph.set_xlabel("Sample ()")
muTemperature = statistics.mean(temperatureListHistogram)
#global varmuTemperature
#varmuTemperature.set("Temperature: " )
if (len(temperatureList) > 1):
sigmaTemperature = statistics.pstdev(temperatureListHistogram)
else:
sigmaTemperature = 100
temperatureGraphHistogram.clear()
nTemperature, binsTemperature, patchesTemperature = temperatureGraphHistogram.hist(temperatureListHistogram, 100, density=False, facecolor='red', alpha=0.75, histtype = 'stepfilled')
normalDistTemperature = scipy.stats.norm.pdf(binsTemperature, muTemperature, sigmaTemperature)
temperatureGraphHistogramNormal.clear()
temperatureGraphHistogramNormal.plot(binsTemperature, normalDistTemperature, 'b--')
temperatureGraphHistogram.set_title("Histogram for Temperature Data")
temperatureGraphHistogram.set_ylabel("Temperature RAW Counts (Dec)")
temperatureGraphHistogram.set_xlabel("BINS ()")
temperatureGraphHistogramNormal.set_ylabel("Normal Distribution")
if (cnt > 100):
xList.pop(0)
humidityList.pop(0)
temperatureList.pop(0)
cnt+=1
except(OSError):
bla=0
win = make_window()
ani = animation.FuncAnimation(f, animate, interval = 300)
make_window.mainloop()
Debugging a bit and start commenting lines of code, I see that the problem may come from the
f=plt.figure(0, figsize=(20,10))
Commenting this line (and all dependencies of this) makes the Label to be written.
Can someone help here, please? I don't get why the graphics can interfere in the Label.
Thanks a lot.
The general problem seems to be your management of instance variables and objects. You keep around as self.* variables, things you'll never need to reference again, like buttonCheckComAvailable, but fail to make self.* variables for things, like varmuTemperature, that you will need to reference later.
Object-wise, you do things that don't make sense:
make_window.mainloop()
as make_window is an object class, not an instance, and an instance of the class make_window won't respond to mainloop anyway as it contains a window but isn't one itself.
Here's my MCVE for your example code that makes varmuTemperature an instance variable and, just for demonstration purposes, sets it when the various buttons on the interface are clicked so you can see it's working:
from tkinter import *
from tkinter import ttk
class make_window():
def __init__(self):
self.win = Tk()
self.win.title("Test")
self.win.state("zoomed")
Frame1 = Frame(self.win)
self.comboBoxAvailableCOMPort = ttk.Combobox(Frame1, width=30)
self.comboBoxAvailableCOMPort['values'] = []
self.comboBoxAvailableCOMPort.pack(padx=5, pady=5, side=LEFT)
Button(Frame1, text="Check COM Available", command=self.CheckComAvailable).pack(padx=5, pady=10, side=LEFT)
Button(Frame1, text="Open COM Port", command=self.OnOpenCom).pack(padx=5, pady=10, side=LEFT)
Button(Frame1, text="Close COM Port", command=self.OnCloseCom).pack(padx=5, pady=10, side=LEFT)
Frame1.pack()
Frame2 = Frame(self.win, highlightbackground="red", highlightcolor="red", highlightthickness=1)
self.varmuTemperature = StringVar(value="default value")
Label(Frame2, textvariable=self.varmuTemperature).pack()
Button(Frame2, text="Close Program", command=self.OnCloseProgram).pack(expand=True, fill='x', anchor='s')
Frame2.pack()
def CheckComAvailable(self):
self.varmuTemperature.set("CheckCom")
def OnOpenCom(self):
self.varmuTemperature.set("OpenCom")
def OnCloseCom(self):
self.varmuTemperature.set("CloseCom")
def OnCloseProgram(self):
self.OnCloseCom()
exit()
window = make_window()
window.win.mainloop()

Categories