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.
Related
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()
class calculate:
def __init__(self):
self.window = tk.Tk()
self.numbers = tk.Frame(master = self.window)
self.signs = tk.Frame(master = self.window)
self.lst, self.lst2 = (1,2,3,4,5,6,7,8,9,0,".","="),("C","+","-","*","/")
def buttons(self):
val = 0
for i in range(4):
for j in range(3):
self.numbutton = tk.Button(master = self.numbers,text = self.lst[val])
self.numbutton.grid(row = i,column = j)
val += 1
for i in range(5):
self.signbutton = tk.Button(master = self.signs,text = self.lst2[i])
self.signbutton.grid(row = i)
def packing(self):
self.numbers.grid(column= 0,row = 0,rowspan = 4)
self.signs.grid(column = 1,row = 0,rowspan = 4)
self.window.mainloop()
calculator = calculate()
calculator.buttons()
calculator.packing()
I am trying to put some buttons by using gird in python tkinter.
and I want that numbers and signs have same height.
but the signs is of larger height.
please help.
Thanks for help in advance.
You can force the size of a frame by giving the frame an height:
self.numbers = tk.Frame(master = self.window, height=200)
self.signs = tk.Frame(master = self.window, height=200)
Both frames will have a height of 200 but you can change it to whatever you want
for i in range(4):
for j in range(3):
self.numbutton = tk.Button(master = self.numbers,text = self.lst[val],width=5,height=5)
self.numbutton.grid(row = i,column = j)
val += 1
for i in range(5):
self.signbutton = tk.Button(master = self.signs,text = self.lst2[i],width=5, height=4)
self.signbutton.grid(row = i)
Add a padding in y of some 0.1 or 0.2 to make it look good.
You can add sticky="nsew" to all .grid(...) and add self.numbers.rowconfigure("all", weight=1) to make the number buttons to use all the available space inside self.numbers frame.
Below is modified code:
import tkinter as tk
class calculate:
def __init__(self):
self.window = tk.Tk()
self.numbers = tk.Frame(master=self.window)
self.signs = tk.Frame(master=self.window)
self.lst, self.lst2 = (1,2,3,4,5,6,7,8,9,0,".","="), ("C","+","-","*","/")
def buttons(self):
val = 0
for i in range(4):
for j in range(3):
self.numbutton = tk.Button(master=self.numbers, text=self.lst[val])
self.numbutton.grid(row=i, column=j, sticky="nsew") # added sticky
val += 1
for i in range(5):
self.signbutton = tk.Button(master=self.signs, text=self.lst2[i])
self.signbutton.grid(row=i, sticky="nsew") # added sticky
def packing(self):
self.numbers.grid(column=0, row=0, sticky="nsew") # added sticky
self.signs.grid(column=1, row=0, sticky="nsew") # added sticky
self.numbers.rowconfigure("all", weight=1) # use all available vertical space
self.window.mainloop()
calculator = calculate()
calculator.buttons()
calculator.packing()
I have encountered a problem while coding in Python that I cannot find a solution to. I have a tkinter grid that automatically resizes to fit the size of window it is in, but I want the text within the buttons/labels to also resize to fit the new grid. A basic example of this is below:
from tkinter import *
root = Tk()
for x in range(3):
for y in range(3):
Label(root, width = 2, height = 4, text = (y * 3 + x) + 1, relief = GROOVE).grid(row = y, column = x, sticky = N+E+S+W)
Grid.rowconfigure(root, y, weight = 1)
Grid.columnconfigure(root, x, weight = 1)
root.mainloop()
I would like the text to increase as you drag the window to expand it. How can this be done?
EDIT:
Mike - SMT's solution works well, however not with the situation I described in the comment on his reply.
Here is the code I have:
from tkinter import *
import tkinter.font as tkFont
grey = [0,1,2,6,7,8,9,10,11,15,16,17,18,19,20,24,25,26,30,31,32,39,40,41,48,49,50,54,55,56,60,61,62,63,64,65,69,70,71,72,73,74,78,79,80]
btn_list = []
filled = []
filled_ID = []
class GUI(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.screen_init()
self.key = None
self.keydetection = False
self.detect()
self.bind_all("<Key>", self.key_event)
def screen_init(self, master = None):
for x in range(9):
for y in range(9):
btn_list.append(Button(master, width = 40, height = 40, image = pixel, compound = CENTER, bg = "#D3D3D3" if 9 * y + x in grey else "#FFFFFF", command = lambda c = 9 * y + x: self.click(c)))
btn_list[-1].grid(row = y, column = x, sticky = N+E+S+W)
Grid.rowconfigure(root, y, weight = 1)
Grid.columnconfigure(root, x, weight = 1)
def update(self, btn_ID, number = None, colour = "#000000", master = None):
y = btn_ID // 9
x = btn_ID % 9
Button(master, width = 40, height = 40, image = pixel, text = number, compound = CENTER, fg = colour, bg = "#D3D3D3" if 9 * y + x in grey else "#FFFFFF", command = lambda c = 9 * y + x: self.click(c)).grid(row = y, column = x, sticky = N+E+S+W)
def detect(self):
self.keydetection = not self.keydetection
def key_event(self, event):
try:
self.key = int(event.keysym)
except:
print("Numbers Only!")
def click(self, btn_ID):
if btn_ID in filled:
filled_ID.pop(filled.index(btn_ID))
filled.remove(btn_ID)
window.update(btn_ID)
else:
filled.append(btn_ID)
filled_ID.append(self.key)
window.update(btn_ID, self.key)
def font_resize(event=None):
for btn in btn_list:
x = btn.winfo_width()
y = btn.winfo_height()
if x < y:
btn.config(font=("Courier", (x-10)))
else:
btn.config(font=("Courier", (y-10)))
if __name__ == '__main__':
root = Tk()
root.title("Example")
pixel = PhotoImage(width = 1, height = 1)
font = tkFont.Font(family = "Helvetica")
window = GUI(root)
root.bind("<Configure>", font_resize)
root.mainloop()
This means that the text is not in the grid when the code is originally run as the user presses a number key to select a number, and then clicks where they would like that number to appear in the grid, and this seems to break Mike - SMT's solution. Any fixes?
I would use a list to keep track of each label. Then apply a new text size to the label when the window resizes. You will also need to provide a 1x1 pixel image in order for the label to read its size in pixels vs text height.
I will be using grid() for this but it could just as easily be done with pack().
Because we are using grid() we need to make sure we set weights on each column/row the labels will be set to using rowconfigure() and columnconfigure().
The function we will need should check the height and width of the widget and then based on what number is smaller set the size of the font object. This will prevent the font from growing bigger than the label size.
import tkinter as tk
import tkinter.font as tkFont
root = tk.Tk()
label_list = []
font = tkFont.Font(family="Helvetica")
pixel = tk.PhotoImage(width=1, height=1)
for x in range(3):
for y in range(3):
root.rowconfigure(y, weight=1)
root.columnconfigure(x, weight=1)
label_list.append(tk.Label(root, width=40, height=40, image=pixel, text=(y * 3 + x) + 1, relief="groove", compound="center"))
label_list[-1].grid(row=x, column=y, sticky="nsew")
def font_resize(event=None):
for lbl in label_list:
x = lbl.winfo_width()
y = lbl.winfo_height()
if x < y:
lbl.config(font=("Courier", (x-10)))
else:
lbl.config(font=("Courier", (y-10)))
root.bind( "<Configure>", font_resize)
root.mainloop()
Results:
No mater how you resize the window it should always have roughly the largest the font can be without exceeding the label size.
UPDATE:
I did change a few things in your code. I changed how you were creating your btn_ID to something less complicated and something we can use with the update method. Let me know if you have any questions.
To answer your changed question and your comment here is a reworked version of your new code to do what you want:
from tkinter import *
import tkinter.font as tkFont
grey = [0,1,2,6,7,8,9,10,11,15,16,17,18,19,20,24,25,26,30,31,32,39,40,41,48,49,50,54,55,56,60,61,62,63,64,65,69,70,71,72,73,74,78,79,80]
class GUI(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pixel = PhotoImage(width=1, height=1)
self.font = tkFont.Font(family="Helvetica")
self.master.bind("<Configure>", self.font_resize)
self.btn_list = []
self.filled = []
self.filled_ID = []
self.screen_init()
self.key = None
self.keydetection = False
self.detect()
self.bind_all("<Key>", self.key_event)
root.bind( "<Configure>", self.font_resize)
def screen_init(self, master=None):
btn_ndex = 0
for x in range(9):
for y in range(9):
self.btn_list.append(Button(master, font=self.font, width=40, height=40, image=self.pixel, compound=CENTER, bg="#D3D3D3" if 9 * y + x in grey else "#FFFFFF", command=lambda c=btn_ndex: self.click(c)))
self.btn_list[-1].grid(row=y, column=x, sticky="nsew")
root.rowconfigure(y, weight=1)
root.columnconfigure(x, weight=1)
btn_ndex += 1
def font_resize(self, event=None):
for btn in self.btn_list:
x = btn.winfo_width()
y = btn.winfo_height()
if x < y:
self.font.configure(size=x-10)
else:
self.font.configure(size=y-10)
def update(self, btn_ID, number=None, colour="#000000", master=None):
print(btn_ID)
y = btn_ID // 9
x = btn_ID % 9
self.btn_list[btn_ID].config(text=number, fg=colour, bg="#D3D3D3" if 9 * y + x in grey else "#FFFFFF", command=lambda c=9 * y + x: self.click(c))
def detect(self):
self.keydetection=not self.keydetection
def key_event(self, event):
try:
self.key=int(event.keysym)
except:
print("Numbers Only!")
def click(self, btn_ID):
if btn_ID in self.filled:
self.filled_ID.pop(self.filled.index(btn_ID))
self.filled.remove(btn_ID)
window.update(btn_ID)
else:
self.filled.append(btn_ID)
self.filled_ID.append(self.key)
window.update(btn_ID, self.key)
if __name__ == '__main__':
root = Tk()
root.title("Example")
window = GUI(root)
root.mainloop()
In my case I found this works while coding in tkinter in python:
Welcome.place(relx=0.5, rely=0.1,anchor-=CENTER)
Obviously, you can change the widget name, the x axis and the y axis
Is that how do you want? I did not make any minor changes. In line 24, In the button, I added keyword text.
btn_list.append(Button(master, width = 40, height = 40, image = pixel, text = (y * 3 + x) + 1, compound = CENTER, bg = "#D3D3D3" if 9 * y + x in grey else "#FFFFFF", command = lambda c = 9 * y + x: self.click(c)))
Result before:
After clicking the button to remove the number.
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()
I'm building a UI where the users have to select some dates. I've added a row called Date Indicator which shows a box with a color. The starting color (until the user selects both dates) is grey. This color has to change depending on the dates that the user selects.
If he selects two dates where the days between them are more than two weeks, the box has to became green.
If he selects two dates where the days between them are less than two weeks, the box has to became yellow.
If he selects two dates where the days between them are less than one week, the box has to became red.
This is a Photoshoped result for the desired solution:
The code seems to be long, but you only have to check it until the tkCalendar class. From there to the bottom is just the calendar code you dont need to change. (And yes, there are some imports you dont need to use for this code, I maintain them because I've to use them for other stuff, but you can remove them)
# -*- coding: utf-8 -*-
import os
from Tkinter import *
import Image
import ImageTk
import tkFileDialog
import xlrd
import csv
from tkMessageBox import *
from win32com.client import Dispatch
import datetime
import time
import calendar
year = time.localtime()[0]
month = time.localtime()[1]
day =time.localtime()[2]
strdate = (str(year) + "/" + str(month) + "/" + str(day))
fnta = ("Helvetica", 10)
fnt = ("Helvetica", 10)
fntc = ("Helvetica", 10, 'bold')
strtitle = "Calendario"
strdays= "Do Lu Ma Mi Ju Vi Sa"
dictmonths = {'1':'Ene','2':'Feb','3':'Mar','4':'Abr','5':'May',
'6':'Jun','7':'Jul','8':'Ago','9':'Sep','10':'Oct','11':'Nov',
'12':'Dic'}
class Planificador(Frame):
def __init__(self,master):
Frame.__init__(self, master)
self.master = master
self.initUI()
def initUI(self):
self.master.title("Planner")
self.frameOne = Frame(self.master)
self.frameOne.grid(row=0,column=0)
self.frameTwo = Frame(self.master)
self.frameTwo.grid(row=0, column=1)
self.frameThree = Frame(self.master)
self.frameThree.grid(row=0, column=2)
self.frameFour = Frame(self.master)
self.frameFour.grid(row=1,column=0, sticky=N)
self.frameFive = Frame(self.master)
self.frameFive.grid(row=1, column=1)
self.frameSix = Frame(self.master)
self.frameSix.grid(row=1, column=2, sticky=N)
self.frameSeven = Frame(self.master)
self.frameSeven.grid(row=2,column=0)
self.frameEight = Frame(self.master)
self.frameEight.grid(row=2, column=1, sticky=S)
self.frameNine = Frame(self.master)
self.frameNine.grid(row=2, column=2)
self.start_date_menu()
def start_date_menu(self):
self.initial_num_elements = 4
self.TEXT_MENU_ROW = 0 # initial date menu grid row
self.COL_WIDTH = 10 # width of each subcolumn of dates
for frame in (self.frameFour, self.frameSix):
self.dayintext = Label(frame, text="Day in",
width=self.COL_WIDTH, justify="center")
self.dayintext.grid(row=self.TEXT_MENU_ROW, column=0)
self.dayouttext = Label(frame, text="Day out", width=self.COL_WIDTH,
justify="center")
self.dayouttext.grid(row=self.TEXT_MENU_ROW, column=1)
self.status = Label(frame, text="Date indicator", width=self.COL_WIDTH,
justify="center")
self.status.grid(row=self.TEXT_MENU_ROW, column=2)
self.dates = [self.create_all_entrys(aux_index)
for aux_index in xrange(self.initial_num_elements)]
self.anadirpiezas = Button(self.frameEight, text="add more",
command=self.addone, width=self.COL_WIDTH)
self.anadirpiezas.grid(row=0, column=3)
def addone(self):
self.dates.append(self.create_all_entrys(len(self.dates)))
self.printdates()
def printdates(self):
print "IN:"
for i in xrange(self.initial_num_elements):
print self.dates[i][0].get() # col 0
print "OUT:"
for i in xrange(self.initial_num_elements):
print self.dates[i][2].get() # col 2
def create_all_entrys(self, aux_index):
menu_col = aux_index % 2 # left/right column of the date frame
menu_row = self.TEXT_MENU_ROW + aux_index/2 + 1
frame = self.frameSix if aux_index % 2 else self.frameFour
in_var = StringVar(value="--------")
in_btn = Button(frame, textvariable=in_var, width=self.COL_WIDTH,
command=lambda v=in_var: self.fnCalendar(v))
in_btn.grid(row=menu_row, column=0)
out_var = StringVar(value="--------")
out_btn = Button(frame, textvariable=out_var, width=self.COL_WIDTH,
command=lambda v=out_var: self.fnCalendar(v))
out_btn.grid(row=menu_row, column=1)
self.colorvar = StringVar()
self.colorvar.set('grey')
self.status_color = Label(frame, width=2, bg=self.colorvar.get())
self.status_color.grid(row=menu_row,column=2)
return in_var, in_btn, out_var, out_btn
def fnCalendar(self, datebar):
tkCalendar(self.master, year, month, day, datebar)
class tkCalendar :
def __init__ (self, master, arg_year, arg_month, arg_day,
arg_parent_updatable_var):
print arg_parent_updatable_var.get()
self.update_var = arg_parent_updatable_var
top = self.top = Toplevel(master)
top.title("Choose a date")
try : self.intmonth = int(arg_month)
except: self.intmonth = int(1)
self.canvas =Canvas (top, width =200, height =220,
relief =RIDGE, background ="#ece9d8", borderwidth =0)
self.canvas.create_rectangle(0,0,303,30, fill="#ece9d8",width=0 )
self.canvas.create_text(100,17, text="Choose!", font=fntc, fill="#BA1111")
stryear = str(arg_year)
self.year_var=StringVar()
self.year_var.set(stryear)
self.lblYear = Label(top, textvariable = self.year_var,
font = fnta, background="#ece9d8")
self.lblYear.place(x=85, y = 30)
self.month_var=StringVar()
strnummonth = str(self.intmonth)
strmonth = dictmonths[strnummonth]
self.month_var.set(strmonth)
self.lblYear = Label(top, textvariable = self.month_var,
font = fnta, background="#ece9d8")
self.lblYear.place(x=85, y = 50)
#Variable muy usada
tagBaseButton = "Arrow"
self.tagBaseNumber = "DayButton"
#draw year arrows
x,y = 40, 43
tagThisButton = "leftyear"
tagFinalThisButton = tuple((tagBaseButton,tagThisButton))
self.fnCreateLeftArrow(self.canvas, x,y, tagFinalThisButton)
x,y = 150, 43
tagThisButton = "rightyear"
tagFinalThisButton = tuple((tagBaseButton,tagThisButton))
self.fnCreateRightArrow(self.canvas, x,y, tagFinalThisButton)
#draw month arrows
x,y = 40, 63
tagThisButton = "leftmonth"
tagFinalThisButton = tuple((tagBaseButton,tagThisButton))
self.fnCreateLeftArrow(self.canvas, x,y, tagFinalThisButton)
x,y = 150, 63
tagThisButton = "rightmonth"
tagFinalThisButton = tuple((tagBaseButton,tagThisButton))
self.fnCreateRightArrow(self.canvas, x,y, tagFinalThisButton)
#Print days
self.canvas.create_text(100,90, text=strdays, font=fnta)
self.canvas.pack (expand =1, fill =BOTH)
self.canvas.tag_bind ("Arrow", "<ButtonRelease-1>", self.fnClick)
self.canvas.tag_bind ("Arrow", "<Enter>", self.fnOnMouseOver)
self.canvas.tag_bind ("Arrow", "<Leave>", self.fnOnMouseOut)
self.fnFillCalendar()
def fnCreateRightArrow(self, canv, x, y, strtagname):
canv.create_polygon(x,y, [[x+0,y-5], [x+10, y-5] , [x+10,y-10] ,
[x+20,y+0], [x+10,y+10] , [x+10,y+5] , [x+0,y+5]],
tags = strtagname , fill="black", width=0)
def fnCreateLeftArrow(self, canv, x, y, strtagname):
canv.create_polygon(x,y, [[x+10,y-10], [x+10, y-5] , [x+20,y-5] ,
[x+20,y+5], [x+10,y+5] , [x+10,y+10] ],
tags = strtagname , fill="black", width=0)
def fnClick(self,event):
owntags =self.canvas.gettags(CURRENT)
if "rightyear" in owntags:
intyear = int(self.year_var.get())
intyear +=1
stryear = str(intyear)
self.year_var.set(stryear)
if "leftyear" in owntags:
intyear = int(self.year_var.get())
intyear -=1
stryear = str(intyear)
self.year_var.set(stryear)
if "rightmonth" in owntags:
if self.intmonth < 12 :
self.intmonth += 1
strnummonth = str(self.intmonth)
strmonth = dictmonths[strnummonth]
self.month_var.set(strmonth)
else :
self.intmonth = 1
strnummonth = str(self.intmonth)
strmonth = dictmonths[strnummonth]
self.month_var.set(strmonth)
intyear = int(self.year_var.get())
intyear +=1
stryear = str(intyear)
self.year_var.set(stryear)
if "leftmonth" in owntags:
if self.intmonth > 1 :
self.intmonth -= 1
strnummonth = str(self.intmonth)
strmonth = dictmonths[strnummonth]
self.month_var.set(strmonth)
else :
self.intmonth = 12
strnummonth = str(self.intmonth)
strmonth = dictmonths[strnummonth]
self.month_var.set(strmonth)
intyear = int(self.year_var.get())
intyear -=1
stryear = str(intyear)
self.year_var.set(stryear)
self.fnFillCalendar()
def fnFillCalendar(self):
init_x_pos = 20
arr_y_pos = [110,130,150,170,190,210]
intposarr = 0
self.canvas.delete("DayButton")
self.canvas.update()
intyear = int(self.year_var.get())
monthcal = calendar.monthcalendar(intyear, self.intmonth)
for row in monthcal:
xpos = init_x_pos
ypos = arr_y_pos[intposarr]
for item in row:
stritem = str(item)
if stritem == "0":
xpos += 27
else :
tagNumber = tuple((self.tagBaseNumber,stritem))
self.canvas.create_text(xpos, ypos , text=stritem,
font=fnta,tags=tagNumber)
xpos += 27
intposarr += 1
self.canvas.tag_bind ("DayButton", "<ButtonRelease-1>", self.fnClickNumber)
self.canvas.tag_bind ("DayButton", "<Enter>", self.fnOnMouseOver)
self.canvas.tag_bind ("DayButton", "<Leave>", self.fnOnMouseOut)
def fnClickNumber(self,event):
owntags =self.canvas.gettags(CURRENT)
for x in owntags:
if (x == "current") or (x == "DayButton"): pass
else :
strdate = (str(self.year_var.get()) + "/" +
str(self.intmonth) + "/" +
str(x))
self.update_var.set(strdate)
self.top.withdraw()
def fnOnMouseOver(self,event):
self.canvas.move(CURRENT, 1, 1)
self.canvas.update()
def fnOnMouseOut(self,event):
self.canvas.move(CURRENT, -1, -1)
self.canvas.update()
if __name__ == "__main__":
root = Tk()
aplicacion = Planificador(root)
root.mainloop()
I'm totally lost trying to do this. I know I've to change self.colorvar but I dont know how to do it. I tryed to self.colorvar.set('red') for example but I cant make it work the way I explaned above. The in and out dates can be found on the printdates function. You help is much appreciated.
I don't think you can link the bg option to a StringVar. You can only do it with the option textvariable. The way you should do it is via the Label.config() method.
So you could write:
self.status_color.config(bg='red')
or, if you want to keep using the colorvar:
self.colorvar.set('red')
self.status_color.config(bg=self.colorvar.get())
That should work, and I guess that you can figure out the 2 week thing.
EDIT:
You can also 'link' the StringVar self.colorvar to a function that changes self.status_color (with the trace method); so that whenever colorvar is changed, your function will be called and it will change the status color.
This is my suggestion:
def myFunc(*args):
self.status_color.config(bg=self.colorvar.get())
self.colorvar.trace('w',self.myFunc)
Now every time you call self.colorvar.set(), you will be also calling self.myFunc.
EDIT:
As for the week thing, I'm not going to answer completely as it seems easy. Just get an integer for month and day of IN and OUT and do something like this:
monthdiff = monthout-monthin
if monthdiff!=0:
dayout += monthdiff*30 #or 31, depending on the month. Just add an if/else
weeks = ((dayout-dayin)//7)+1 #For python2.7 use only one slash
And then your if/else with the colour changing function. Be careful with January too, just add one if more. And I guess you can parse the IN and OUT strings to get the month and day yourself.