Related
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()
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.
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)
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 want to add a Circular progress bar to my Python GUI using Tkinter, but I didn't find any documentation for Circular progress bars with Tkinter.
How can I create a Circular progress bar in Tkinter or is this not possible?
There's none built-in, but you can create one of your own. Here's an example of one way of doing it that manually draws and updates the graphics on a Canvas:
try:
import Tkinter as tk
import tkFont
import ttk
except ImportError: # Python 3
import tkinter as tk
import tkinter.font as tkFont
import tkinter.ttk as ttk
class CircularProgressbar(object):
def __init__(self, canvas, x0, y0, x1, y1, width=2, start_ang=0, full_extent=360.):
self.custom_font = tkFont.Font(family="Helvetica", size=12, weight='bold')
self.canvas = canvas
self.x0, self.y0, self.x1, self.y1 = x0+width, y0+width, x1-width, y1-width
self.tx, self.ty = (x1-x0) / 2, (y1-y0) / 2
self.width = width
self.start_ang, self.full_extent = start_ang, full_extent
# draw static bar outline
w2 = width / 2
self.oval_id1 = self.canvas.create_oval(self.x0-w2, self.y0-w2,
self.x1+w2, self.y1+w2)
self.oval_id2 = self.canvas.create_oval(self.x0+w2, self.y0+w2,
self.x1-w2, self.y1-w2)
self.running = False
def start(self, interval=100):
self.interval = interval # Msec delay between updates.
self.increment = self.full_extent / interval
self.extent = 0
self.arc_id = self.canvas.create_arc(self.x0, self.y0, self.x1, self.y1,
start=self.start_ang, extent=self.extent,
width=self.width, style='arc')
percent = '0%'
self.label_id = self.canvas.create_text(self.tx, self.ty, text=percent,
font=self.custom_font)
self.running = True
self.canvas.after(interval, self.step, self.increment)
def step(self, delta):
"""Increment extent and update arc and label displaying how much completed."""
if self.running:
self.extent = (self.extent + delta) % 360
self.canvas.itemconfigure(self.arc_id, extent=self.extent)
# Update percentage value displayed.
percent = '{:.0f}%'.format(
round(float(self.extent) / self.full_extent * 100))
self.canvas.itemconfigure(self.label_id, text=percent)
self.canvas.after(self.interval, self.step, delta)
def toggle_pause(self):
self.running = not self.running
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.createWidgets()
def createWidgets(self):
self.canvas = tk.Canvas(self, width=200, height=200, bg='white')
self.canvas.grid(row=0, column=0, columnspan=2)
self.progressbar = CircularProgressbar(self.canvas, 0, 0, 200, 200, 20)
self.pauseButton = tk.Button(self, text='Pause', command=self.pause)
self.pauseButton.grid(row=1, column=0)
self.quitButton = tk.Button(self, text='Quit', command=self.quit)
self.quitButton.grid(row=1, column=1)
def start(self):
self.progressbar.start()
self.mainloop()
def pause(self):
self.progressbar.toggle_pause()
if __name__ == '__main__':
app = Application()
app.master.title('Sample application')
app.start()
Here's some screenshots of it running:
I dont have enough rep to add a comment but the code that martineau put up as is, does not currently work and you need to change
if self.running:
self.cur_extent = (self.cur_extent + delta) % 360
self.canvas.itemconfigure(self.arc_id, extent=self.cur_extent)
to
if self.running:
self.extent = (self.extent + delta) % 360
self.cur_extent = (self.extent + delta) % 360
self.canvas.itemconfigure(self.arc_id, extent=self.cur_extent)
Tkinter does not have any support for circular progress bars. You will have to draw your own using a series of images, or a drawing on a canvas.