update pi_graph values in FigureCanvasTkAgg without destroying the frame - python

I am creating a pi graph where the data will be passed dynamically so created a method to pass variables so that it displays image on canvas but, I am not able to update data of canvas data , so should every time need to destroy the frame and create the total process again so
from tkinter import *
import numpy as np
import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
root = Tk()
root.geometry("500x500")
root.title("Pi Graph")
cash = IntVar()
credit = IntVar()
pi_frame_main = Frame(root)
pi_frame_main.pack(side="bottom", expand=True, fill="both")
def plot(cash_amount, credit_amount):
pi_frame = Frame(pi_frame_main, height=300, borderwidth=4)
def func(pct, values):
"""calculating the percentage of the values"""
absolute = int(pct / 100.0 * np.sum(values))
return "{:.1f}%\n({:d})".format(pct, absolute)
if cash_amount != 0 or credit_amount != 0:
# create frame for plotting
pi_frame.pack(side='right', padx=10, pady=10, fill='y')
my_dict = {'NAME': ['Cash', 'Credit'], 'Nos': [cash_amount, credit_amount]}
print(my_dict)
df = pd.DataFrame(data=my_dict)
lbl = ['Cash', 'Credit']
explode = [0.0, 0.1]
fig1 = df.plot.pie(title="Cash & Credit", y='Nos', autopct=lambda pct: func(pct, my_dict['Nos']),
explode=explode, figsize=(3,3), labels=lbl, shadow=True,
legend=False).get_figure()
fig1.legend(lbl, bbox_to_anchor=(0.75, 1), loc="upper left")
plot1 = FigureCanvasTkAgg(fig1, pi_frame)
plot1.get_tk_widget().pack(side='right', fill='y')
Entry(root, textvariable=cash).pack(side="left", padx=5)
Entry(root, textvariable=credit).pack(side="left", padx=5)
Button(root, text="Plot", command=lambda: plot(cash.get(), credit.get())).pack(side="right", padx=5)
root.mainloop()
needed some guidance how to update the the pi graph without destroying the frame

Related

How can I plot user chosen quadratics in the form ax^2 + bx +c using matplotlib with tkinter?

So far I have this code. When I try it without tkinter and just put the values for a,b and c into the code it works fine. However, when I try to grab the values from the tkinter entry boxes, it produces an error and says "numpy.core._exceptions._UFuncNoLoopError: ufunc 'multiply' did not contain a loop with signature matching types (dtype('<U1'), dtype('float64')) -> None". How can I make it work?
from tkinter import *
import matplotlib.pyplot as plt
import numpy as np
root = Tk()
yLbl = Label(root, text="y=", pady=30)
yLbl.grid(row=0, column=0)
aEntry = Entry(root, width=2)
aEntry.grid(row=0, column=1)
aLbl = Label(root, text='x\u00B2 +', pady=30)
aLbl.grid(row=0, column=2)
bEntry = Entry(root, width=2)
bEntry.grid(row=0, column=3)
bLbl = Label(root, text='x +', pady=30)
bLbl.grid(row=0, column=4)
cEntry = Entry(root, width=2)
cEntry.grid(row=0, column=5)
def btnGraph():
x = np.linspace(-5,5,1000)
a = aEntry.get()
b = bEntry.get()
c = cEntry.get()
y = a*x**2 + b*x + c
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
plt.plot(x,y, 'r')
plt.show()
btn = Button(root, text="Graph", command=btnGraph)
btn.grid(row=2, column=0)
root.mainloop()
Here is a link to using variables in tkinter https://www.askpython.com/python-modules/tkinter/tkinter-intvar
Look at the defining the tkinter IntVar() variable and Retrieving Values Of IntVar() Variables sections. You can then get the variable into a regular python variable then append it to a list or numpy array. Might seem redundant but this is a great way to get the entry variables.

how to instantly update the colorbar range of a matplotlib from values of entries?

I have a simple code to plot a figure. I want to manually change the range for the colorbar.
So, I added two Entries and defined a second function change(). I want to make this change for the colorbar to happen instantly without having the second button.
from tkinter import *
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
root = Tk()
root.geometry("500x500")
Max, Min = IntVar(), IntVar()
label1 = Label(root, text="Max")
label1.place(x=10, y=35)
label2 = Label(root, text="Min")
label2.place(x=10, y=60)
entry1 = Entry(root, textvariable=Max, width=5)
entry1.place(x=50, y=35)
entry2 = Entry(root, textvariable=Min, width=5)
entry2.place(x=50, y=60)
def plot():
global x, y
x, y = np.mgrid[slice(0, 100), slice(0, 100)]
z = (x*y)
figure = Figure(figsize=(4, 4))
ax = figure.add_subplot(111)
c = ax.pcolormesh(x, y, z, cmap='YlGn')
ax.figure.colorbar(c)
canvas = FigureCanvasTkAgg(figure, root)
canvas.get_tk_widget().place(x=0, y=80)
def change():
z = (x*y)
figure = Figure(figsize=(4, 4))
ax = figure.add_subplot(111)
c = ax.pcolormesh(x, y, z, cmap='YlGn', vmin=entry1.get(), vmax=entry2.get())
ax.figure.colorbar(c)
canvas = FigureCanvasTkAgg(figure, root)
canvas.get_tk_widget().place(x=0, y=80)
button1 = Button(root, text="Plot", command=plot)
button1.place(x=30, y=0)
button2 = Button(root, text="change", command=change)
button2.place(x=80, y=0)
root.mainloop()
I found this post Constantly Update Label Widgets From Entry Widgets TKinter, and I tried to use method 2, and I changed the code in this part:
...
def auto():
c.config(vmin=entry1.get(), vmax=entry2.get())
entry1 = Entry(root, textvariable=Max, width=5)
entry1.place(x=50, y=35)
entry2 = Entry(root, textvariable=Min, width=5)
entry2.place(x=50, y=60)
auto()
...
But as c is a local variable, the code doesn't work. can anybody help me instantly update the colorbar range?
So what You want in the end is to change colormap vmin and vmax, when user change Min and Max input. You don't need to constantly update colormap, but just on change of those inputs.
You can do that by tracing input change with update callback.
Here is modified code which does colormap update when Min and Max input is changed:
from tkinter import *
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
root = Tk()
root.geometry("500x500")
Max, Min = IntVar(), IntVar()
label1 = Label(root, text="Min")
label1.place(x=10, y=35)
label2 = Label(root, text="Max")
label2.place(x=10, y=60)
vmin_entry = Entry(root, textvariable=Min, width=5)
vmin_entry.place(x=50, y=35)
vmax_entry = Entry(root, textvariable=Max, width=5)
vmax_entry.place(x=50, y=60)
# Define global variables
c, canvas = None, None
def plot():
global x, y, c, canvas
x, y = np.mgrid[slice(0, 100), slice(0, 100)]
z = (x * y)
figure = Figure(figsize=(4, 4))
ax = figure.add_subplot(111)
c = ax.pcolormesh(x, y, z, cmap='YlGn')
ax.figure.colorbar(c)
canvas = FigureCanvasTkAgg(figure, root)
canvas.get_tk_widget().place(x=0, y=80)
canvas.draw()
def update_colormap(*args, **kwargs):
global c, canvas
if c is not None:
try:
# Get vmin and vmax
vmin, vmax = int(vmin_entry.get()), int(vmax_entry.get())
except ValueError:
# Could not convert values to int, non integer value
return
if vmin > vmax:
return
# Set new limits
c.set_clim(vmin, vmax)
# Update plot
canvas.flush_events()
canvas.draw()
# Trace change of Min and Max and call update_colormap as a callabck
Min.trace("w", update_colormap)
Max.trace("w", update_colormap)
button1 = Button(root, text="Plot", command=plot)
button1.place(x=30, y=0)
root.mainloop()
You can bind the "<Key>" event of the Entry widget to the change function as the callback, this will call the change function whenever, anything is typed within the entry widget.
entry1.bind('<Key>', lambda x : change())
Doing this for both entries would be adding these lines to your code -:
entry1.bind('<Key>', lambda x : change())
entry2.bind('<Key>', lambda x : change())
The full code will become -:
from tkinter import *
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
root = Tk()
root.geometry("500x500")
Max, Min = IntVar(), IntVar()
label1 = Label(root, text="Max")
label1.place(x=10, y=35)
label2 = Label(root, text="Min")
label2.place(x=10, y=60)
entry1 = Entry(root, textvariable=Max, width=5)
entry1.place(x=50, y=35)
entry2 = Entry(root, textvariable=Min, width=5)
entry2.place(x=50, y=60)
def plot():
global x, y
x, y = np.mgrid[slice(0, 100), slice(0, 100)]
z = (x*y)
figure = Figure(figsize=(4, 4))
ax = figure.add_subplot(111)
c = ax.pcolormesh(x, y, z, cmap='YlGn')
ax.figure.colorbar(c)
canvas = FigureCanvasTkAgg(figure, root)
canvas.get_tk_widget().place(x=0, y=80)
def change():
z = (x*y)
figure = Figure(figsize=(4, 4))
ax = figure.add_subplot(111)
c = ax.pcolormesh(x, y, z, cmap='YlGn', vmin=entry1.get(), vmax=entry2.get())
ax.figure.colorbar(c)
canvas = FigureCanvasTkAgg(figure, root)
canvas.get_tk_widget().place(x=0, y=80)
button1 = Button(root, text="Plot", command=plot)
button1.place(x=30, y=0)
entry1.bind('<Key>', lambda x : change()) # binding the first entry's keypress event to the change function.
entry2.bind('<Key>', lambda x : change()) # binding the second entry's keypress event to the change function.
root.mainloop()
NOTE:
The "<Key>" event's callback is triggered whenever a key is pressed inside the widget, for more info take a look at events and bindings.

Make interactive graph of count history in tkinter

I have the following code for a counter with two buttons, one to increase the count and the other to decrease it. The count is a label containing a number. The graph that appears with the buttons is supposed to visualise the history of the counted number ie. x axis is the index number of results_table and the y axis is the number that appears in the count. The buttons and the count work but the graph doesn't show with the following code. There is obviously something I am missing to get the graph to update. Here's the code:
import tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
class App:
def __init__(self, master):
# Create a container
frame = tkinter.Frame(master)
# Create 2 buttons
self.button_left = tkinter.Button(frame,text="-", command=self.decrease, bg = 'red', fg = 'white')
self.button_left.pack(side="left")
self.button_right = tkinter.Button(frame,text="+", command=self.increase, bg = 'green', fg = 'white')
self.button_right.pack(side="right")
self.label_value = tk.Label(frame, text = '0')
self.label_value.pack(side = "bottom")
fig = Figure()
ax = fig.add_subplot(111)
self.line, = ax.plot(0)
self.canvas = FigureCanvasTkAgg(fig,master=master)
self.canvas.draw()
self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
frame.pack()
result_table = []
def decrease(self):
value = int(self.label_value["text"])
self.label_value["text"] = f"{value - 1}"
result_table.append(self.label_value['text'])
x, y = self.line.get_data('result_table')
self.canvas.draw()
def increase(self):
value = int(self.label_value["text"])
self.label_value["text"] = f"{value + 1}"
result_table.append(self.label_value['text'])
x, y = self.line.get_data('result_table')
self.canvas.draw()
root = tkinter.Tk()
app = App(root)
root.mainloop()
Any help graetly appreciated.
Matt
so I solved your problem, but it is not a straight forward answer/solution.
First of all you designed your GUI quiet good! I just wouldn't use pack() but I prefer grid().
Next, I had to delete your class, because I never used tkinter with a class. Maybe you will be able to put it back in.
So what I did:
As already mentioned by Matiiss, you don't really use your x and y values to plot your figure. Your result_table on the other hand just works fine and stores all values created!
So you have to plot them as y values and your x values are basically dependent on the length of your result_table len(result_table). For this part I used numpy to always generate an array of the appropriate length.
Furthermore, I created an extra container fig_frame inside the root, where I always display the figure.
This code worked for me:
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import numpy as np
def decrease():
value = int(label_value["text"])
label_value["text"] = f"{value - 1}"
result_table.append(label_value['text'])
x = np.arange(len(result_table))
y = result_table
create_fig(x, y)
def increase():
value = int(label_value["text"])
label_value["text"] = f"{value + 1}"
result_table.append(label_value['text'])
x = np.arange(len(result_table))
y = result_table
create_fig(x, y)
def create_fig(x, y):
fig = Figure()
ax = fig.add_subplot(111)
line, = ax.plot(x, y)
canvas = FigureCanvasTkAgg(fig, fig_frame)
canvas.draw()
canvas.get_tk_widget().grid(row=0, column=0)
root = tk.Tk()
# Create a container
frame = tk.Frame(root)
fig_frame = tk.Canvas(root, height=650, width=650, borderwidth=1, relief='ridge')
fig_frame.pack()
# Create 2 buttons
button_left = tk.Button(frame, text="-", command=decrease, bg='red', fg='white')
button_left.pack(side="left")
button_right = tk.Button(frame, text="+", command=increase, bg='green', fg='white')
button_right.pack(side="right")
label_value = tk.Label(frame, text='0')
label_value.pack(side="bottom")
fig = Figure()
ax = fig.add_subplot(111)
line, = ax.plot(0)
canvas = FigureCanvasTkAgg(fig, fig_frame)
canvas.draw()
canvas.get_tk_widget().grid(row=0, column=0)
frame.pack()
result_table = []
root.mainloop()
Keep going like this! You really already did a great work and even if my code looks different to your code, most parts are just rearranged!

Add/change/remove elements in tkinter when specific dropdown menu item is selected

So I am trying to create a "programme" where you can click a calculate button, and then it will calculate and show a plot of some data that are dependent on some options a user chooses, i.e. dropdown menus, input fields, etc.
I have the following code:
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
from tkinter import *
import tkinter.ttk
import numpy as np
import matplotlib.pyplot as plt
class MainPlot:
def __init__(self, frame1):
self.frame1 = frame1
self.frame1a = Frame(frame1)
self.frame1a.pack(side=LEFT, fill=Y, padx=20, pady=20)
self.frame1b = Frame(frame1)
self.frame1b.pack(side=RIGHT, fill=BOTH, expand=True)
options2 = ["var1", "var2", "var3"]
self.variable2 = StringVar()
self.variable2.set("var1") # default value
self.w2 = OptionMenu(self.frame1a, self.variable2, *options2)
self.w2.grid(row=0, column=0, sticky=NW, ipadx=5)
# Variables
if self.variable2.get() == "var1":
self.equipment = {"var11": False,
"var12": True,
"var13": True,
"var14": True
}
elif self.variable2.get() == "var2":
self.equipment = {"var21": False,
"var22": True,
"var23": False
}
# Available nodes
self.available_nodes = Label(self.frame1a, text="Available nodes:")
self.available_nodes.grid(row=1, column=0, sticky=NW, pady=(10, 0))
self.CheckVar, self.C = dict(), dict()
for i, (key, value) in enumerate(self.equipment.items()):
self.CheckVar[key] = IntVar(self.frame1a)
self.CheckVar[key].set(value)
self.C[key] = Checkbutton(self.frame1a, text = key, variable=self.CheckVar[key], onvalue = True, offvalue = False, command=lambda: self.equipment[key].__setitem__(1, self.CheckVar[key].get()))
self.C[key].grid(row=2+i, column=0, sticky=NW)
# Separator
tkinter.ttk.Separator(self.frame1a, orient=HORIZONTAL).grid(column=0, row=3+len(self.equipment), rowspan=1, sticky='we', pady=15, padx=5)
# Calculate button
self.button1 = Button(self.frame1a, text="Calculate")
self.button1.grid(row=4+len(self.equipment), column=0, sticky=N, ipady=5, ipadx=10, padx=2)
if __name__ == '__main__':
root = Tk(className='Testing')
root.geometry("1920x1080")
aframe = Frame(root)
mainplot = MainPlot(aframe)
aframe.pack(side=LEFT, expand=True, fill=BOTH)
root.mainloop()
What I would like to do is that when I select a different option in the dropdown menu, the resulting checkboxes (created from the if, elif statements and dictionaries at # Variables) changes accordingly.
How can this be achieved ?

Pyplot and Tkinter - Unwanted Extra Window

My code below creates an unwanted duplicate window when I try to add a window title using plt.figure().canvas.manager.set_window_title("Custom Title").
I've done some research and discovered that I am probably not supposed to be mixing pyplot and tkinter this way as they get confused. However I couldn't really make sense of the proposed solutions, some of which used something called FigureCanvasTkAgg which I don't know about. I want my plot to be freestanding, just as it is when I remove plt.figure().canvas.manager.set_window_title("Custom Title").
How can I refactor my code please to not violate any principles which my current code does and to remove the unwanted window?
import matplotlib.pyplot as plt
import tkinter as tk
import networkx as nx
NUM_ROWS = 5
BOLD_FONT = ("calbri", 12, "bold")
NORMAL_FONT = ("calbri", 12, "normal")
def create_widgets():
for i in range(NUM_ROWS):
key = chr(i + 65)
this_row = widgets[key] = {}
this_row["label"] = tk.Label(root, text=key, font=BOLD_FONT)
this_row["label"].grid(row=i, column=0, padx=5, pady=10)
this_row["factor_field"] = tk.Entry(root, width=60, font=NORMAL_FONT)
this_row["factor_field"].grid(row=i, column=1, padx=5, pady=10)
this_row["target_node_field"] = tk.Entry(
root, width=5, font=NORMAL_FONT)
this_row["target_node_field"].grid(row=i, column=2, padx=5, pady=10)
submit_button = tk.Button(root, text="Submit", command=submit,
font=BOLD_FONT).grid(row=NUM_ROWS + 1, column=0, padx=5, pady=10)
def submit():
plt.close()
G = nx.DiGraph()
edges = []
for key, row in widgets.items():
factor_field_contents = row["factor_field"].get()
target_node_field_contents = row["target_node_field"].get().upper()
if factor_field_contents != "" and target_node_field_contents != "":
edges.append((key, target_node_field_contents))
data[key] = {"factor": factor_field_contents,
"target_node": target_node_field_contents}
G.add_edges_from(edges)
# pos = nx.spring_layout(G, k=1.0, iterations=50)
pos = nx.spring_layout(G)
nx.draw_networkx_nodes(G, pos, node_size=500, node_color="green")
nx.draw_networkx_labels(G, pos, font_color="white")
nx.draw_networkx_edges(
G, pos, connectionstyle='arc3, rad = 0.1', width=2, arrows=True)
plt.figure().canvas.manager.set_window_title("Custom Title")
plt.show()
if __name__ == "__main__":
data = {}
widgets = {}
root = tk.Tk()
root.title("My App")
create_widgets()
root.mainloop()
You said you found a solution in FigureCanvasTkAgg, but you don't understand it. You have to gain that understanding because it is literally the only way (according to my research). My example should get you started in that understanding. To be honest, I don't know anything, at all, about matplotlib. I just read the docs and fulfilled the requirements. It seems to work perfectly at creating a single window.
#import matplotlib.pyplot as plt #remove this, you can't use it anymore
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import tkinter as tk
import networkx as nx
NUM_ROWS = 5
BOLD_FONT = ("calbri", 12, "bold")
NORMAL_FONT = ("calbri", 12, "normal")
def create_widgets():
for i in range(NUM_ROWS):
key = chr(i + 65)
this_row = widgets[key] = {}
this_row["label"] = tk.Label(root, text=key, font=BOLD_FONT)
this_row["label"].grid(row=i, column=0, padx=5, pady=10)
this_row["factor_field"] = tk.Entry(root, width=60, font=NORMAL_FONT)
this_row["factor_field"].grid(row=i, column=1, padx=5, pady=10)
this_row["target_node_field"] = tk.Entry(
root, width=5, font=NORMAL_FONT)
this_row["target_node_field"].grid(row=i, column=2, padx=5, pady=10)
submit_button = tk.Button(root, text="Submit", command=submit,
font=BOLD_FONT).grid(row=NUM_ROWS + 1, column=0, padx=5, pady=10)
#this is your single window
#I'm sure some of this could be made just once and reused
#I'm also sure this could be made more dynamic
#At least you have the multi-window part solved
def plotter():
global plotwin
plotwin = tk.Toplevel(root)
fig = Figure(figsize=(5,5), dpi=100)
fig.add_subplot(111)
canvas = FigureCanvasTkAgg(fig, plotwin)
canvas._tkcanvas.pack(fill='both', expand=True)
NavigationToolbar2Tk(canvas, plotwin, pack_toolbar=True).update()
def submit():
try:
#if plotter() hasn't been called yet this will throw a NameError
#we simply catch and ignore it
plotwin.destroy()
except NameError as e:
pass
G = nx.DiGraph()
edges = []
for key, row in widgets.items():
factor_field_contents = row["factor_field"].get()
target_node_field_contents = row["target_node_field"].get().upper()
if factor_field_contents != "" and target_node_field_contents != "":
edges.append((key, target_node_field_contents))
data[key] = {"factor": factor_field_contents,
"target_node": target_node_field_contents}
G.add_edges_from(edges)
# pos = nx.spring_layout(G, k=1.0, iterations=50)
pos = nx.spring_layout(G)
nx.draw_networkx_nodes(G, pos, node_size=500, node_color="green")
nx.draw_networkx_labels(G, pos, font_color="white")
nx.draw_networkx_edges(G, pos, connectionstyle='arc3, rad = 0.1', width=2, arrows=True)
#instantiate window
plotter()
if __name__ == "__main__":
data = {}
widgets = {}
root = tk.Tk()
root.title("My App")
create_widgets()
root.mainloop()

Categories