Graphing Program in Python (3) - python

I've been trying to make a GUI in Python that will take 3 numbers as user input and graph them in a 3D scatter plot. i've been successful in creating the GUI through tkinter, but I'm stuck when it comes to actual graphing. I wanted to define an empty list for x, y, and z variables, then append them with whatever the user inputs. I would then take those variables and have matplotlib graph them. I was unable to do it as originally planned, so I reverted to defining two functions, each tied to their own buttons. The "store" button would record the user input into the respective empyt lists created beforehand and clear the entry box. Then, the user could hit the graph button and the lists would be graphed in a 3D plot. Here is the code:
from tkinter import *
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
#store function
x_List = []
y_List = []
z_List = []
def store():
x_List.append(x_Var)
y_List.append(y_Var)
z_List.append(z_Var)
entry_1.delete(0, END)
entry_2.delete(0, END)
entry_3.delete(0, END)
def graph():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X = x_List
Y = y_List
Z = z_List
ax.scatter(X, Y, Z, c='b', marker='o')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
home = Tk()
home.title("Graph")
homeLabel = Label(home, text="Graph Program").grid(row=0, column=1)
home.minsize(600,600)
home.maxsize(600,600)
#entry boxes and labels
intel = StringVar()
person = StringVar()
attractive = StringVar()
label_1 = Label(home, text="X:").grid(row=2, column=0)
label_2 = Label(home, text="Y:").grid(row=3, column=0)
label_3 = Label(home, text="Z:").grid(row=4, column=0)
entry_1 = Entry(home, textvariable=x_Var)
entry_1.grid(row=2, column=1)
entry_2 = Entry(home, textvariable=y_Var)
entry_2.grid(row=3, column=1)
entry_3 = Entry(home, textvariable=z_Var)
entry_3.grid(row=4, column=1)
#buttons
storeButton = Button(home, text="Store", command=store).grid(row=5, column=0)
graphButton = Button(home, text="Graph", command=graph).grid(row=5, column=1)
home.mainloop()

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!

Tkinter real time animation

How I would achieve that the first point that I plot isn't (0,0), (determined by line1, = ax1.plot([0], [0]), but the point that is calculated by func_A in first iteration of update_plot() function.
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
from matplotlib import style
import matplotlib
matplotlib.use("Agg")
root = tk.Tk()
root.title("Graph")
#root.geometry("800x400")
# progress label, pause and resume buttons
frame = tk.Frame(root)
frame.pack(fill="x", side=tk.TOP)
progress = tk.Label(frame)
progress.pack(side="left")
is_paused = tk.BooleanVar() # variable to hold the pause/resume state
tk.Button(frame, text="Pause", command=lambda: is_paused.set(True)).pack(side="right")
tk.Button(frame, text="Resume", command=lambda: is_paused.set(False)).pack(side="right")
# the plot
fig = plt.figure(figsize=(10, 5), dpi=100)
canvas = FigureCanvasTkAgg(fig, master=root)
toolbar = NavigationToolbar2Tk(canvas, root)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
plt.grid("both")
style.use("ggplot")
a = 1
ax1 = plt.subplot(111)
line1, = ax1.plot([0], [0])
def func_A(a, x):
import numpy
data_x = numpy.arange(0, x)
data_y = a * numpy.sin(data_x/5)
return data_x, data_y
# function to update ploat
def update_plot(k=0):
if not is_paused.get():
progress["text"] = f"iteration: {k}"
data_x, data_y = func_A(a, k)
#print("iteration", k)
#print("data_x", data_x)
#print("data_y", data_y)
line1.set_xdata(data_x)
line1.set_ydata(data_y)
ax1.set_ylim([-1, 1])
ax1.set_xlim([0, 100])
plt.grid("both")
canvas.draw()
canvas.flush_events()
k += 1
if k <= 100:
# update plot again after 10ms. You can change the delay to whatever you want
root.after(10, update_plot, k)
update_plot() # start updating plot
root.mainloop()

How can I make a code that displays buttons on one half of a tkinter frame and a plot on the other half?

I have a code here.
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import matplotlib
import math
import numpy as np
root = tk.Tk()
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
x = np.linspace(3,-3,10000)
p = np.sin(x)
w = np.sin(2*x)
l = np.sin(x)/x
c = np.cos(1/x)
r = np.tan(x)
ur = np.cos(x)/x
def firstx():
plt.plot(x, p, 'p-', label = 'y=sin(x)')
fig.canvas.draw()
def snx():
plt.plot(x, w, 'c-', label = 'y=sin(2x)')
fig.canvas.draw()
def thx():
plt.plot(x, l, 'm-', label = 'y=sin(x)/x')
fig.canvas.draw()
def fthx():
plt.plot(x, c, 'v-', label = 'y=sin(1/x)')
fig.canvas.draw()
def tan():
plt.plot(x, r, 's-', label = 'y=tan(x)')
ax.set_ylim([-5, 5])
fig.canvas.draw()
canvas = FigureCanvasTkAgg(fig, master=root)
plot_widget = canvas.get_tk_widget()
plot_widget.grid(row=0, column=1)
tk.Button(root, text="y=sin(x)", command=firstx).grid(row=0, column=0)
tk.Button(root, text="y=sin(2x)", command=snx).grid(row=1, column=0)
tk.Button(root, text="y=sin(x)/x", command=thx).grid(row=2, column=0)
tk.Button(root, text="y=sin(1/x)", command=fthx).grid(row=3, column=0)
tk.Button(root, text="y=tan(x)", command=tan).grid(row=4, column=0)
root.mainloop()
If you run the code you will see one button on the left and the rest of the buttons on the bottom left. How do I make it so that the buttons are all on the left and not the bottom left?
P.S. I am using python 3.6.6.
Change this line:
plot_widget.grid(row=0, column=1)
to
plot_widget.grid(row=0, column=1, rowspan=5)
Read about the rowspan= argument here https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/grid.html or https://effbot.org/tkinterbook/grid.htm

Categories