I need the program to display 2 graphs of mathematical functions on the same coordinate line, which the user enters manually into the input window. How can I make this graphs show up in a tkinter window? I heard about using Figure, but then 2 graphs will not be on the same coordinate line
replacements = {
'sin' : 'np.sin',
'cos' : 'np.cos',
'exp': 'np.exp',
'sqrt': 'np.sqrt',
'^': '**',
}
allowed_words = [
'x',
'sin',
'cos',
'sqrt',
'exp',
]
def string2func(string):
for word in re.findall('[a-zA-Z_]+', string):
if word not in allowed_words:
raise ValueError(
'"{}" is forbidden to use in math expression'.format(word)
)
for old, new in replacements.items():
string = string.replace(old, new)
def func(x):
return eval(string)
return func
def create_plot():
a = int(value1.get())
b = int(value2.get())
x = np.linspace(a, b, 1000)
func1 = str(value3.get())
func2 = str(value4.get())
if func1.isnumeric():
func1 = int(value3.get())
func1 = [func1] * 1000
plt.plot(x, func1 , label = 'F1(X)')
else:
func1 = str(value3.get())
strfunc1 = string2func(func1)
plt.plot(x, strfunc1(x), label ='F1(X)')
if func2.isnumeric():
func2 = int(value4.get())
func2 = [func2] * 1000
plt.plot(x, func2, label = 'F2(X)')
else:
func2 = str(value4.get())
strfunc2 = string2func(func2)
plt.plot(x, strfunc2(x), label ='F2(X)')
plt.grid(True)
plt.xlabel('x')
plt.ylabel('y')
plt.title("Simple Plot")
plt.ylim(-50, 50)
plt.show()
You can get axis and use it to draw another line in the same plot - ax.plot().
Matplotlib has special classes to display in different GUIs (tkinter, PyQt, etc.)
Embedding in Tk — Matplotlib 3.6.0 documentation
Example which draws two lines on one plot (in Tkinter) when you press button.
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import numpy as np
# --- functions ---
def draw_plots():
x = np.arange(0, 3.01, .01)
scale = np.random.randint(1, 10)
y1 = scale * np.sin(scale * np.pi * x)
scale = np.random.randint(1, 10)
y2 = scale * np.sin(scale * np.pi * x)
ax.clear() # remove previous plots
#ax.grid(True)
ax.plot(x, y1)
ax.plot(x, y2)
canvas.draw() # refresh window
# --- main ---
root = tk.Tk()
fig = Figure() # Figure(figsize=(5, 4), dpi=100)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack(fill='both', expand=True) # resize plot when window is resized
ax = fig.add_subplot(111)
#ax.grid(True)
button = tk.Button(root, text='Draw', command=draw_plots)
button.pack()
tk.mainloop()
EDIT:
Example which draws on separated plots
# author: Bartlomiej "furas" Burek (https://blog.furas.pl)
# date: 2022.10.09
# [python - Plots in Tkinter - Stack Overflow](https://stackoverflow.com/questions/73997593/plots-in-tkinter/)
# [Embedding in Tk — Matplotlib 3.6.0 documentation](https://matplotlib.org/stable/gallery/user_interfaces/embedding_in_tk_sgskip.html)
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import numpy as np
# --- functions ---
def draw_plots():
x = np.arange(0, 3.01, .01)
scale = np.random.randint(1, 10)
y1 = scale * np.sin(scale * np.pi * x)
scale = np.random.randint(1, 10)
y2 = scale * np.sin(scale * np.pi * x)
ax1.clear() # remove previous plots
ax2.clear() # remove previous plots
#ax1.grid(True)
#ax2.grid(True)
ax1.plot(x, y1)
ax2.plot(x, y2)
canvas.draw() # refresh window
# --- main ---
root = tk.Tk()
fig = Figure() # Figure(figsize=(5, 4), dpi=100)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack(fill='both', expand=True) # resize plot when window is resized
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
#ax1.grid(True)
#ax2.grid(True)
button = tk.Button(root, text='Draw', command=draw_plots)
button.pack()
tk.mainloop()
Related
I have a tkinter GUI where I'm plotting live data from a sensor using the matplotlib animation. When enough data are collected, a fitting ellipse is calculated. And this ellipse should then be plotted in the same figure as the recorded sensor data.
The original code is a bit lengthy, so here is a piece of working code that shows the problem. Currently the ellipse is drawn in a new window when the animation is stopped by button. Can someone explain me how to add this ellipse to the live plot figure in tkinter GUI?
import tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import pyplot as plt, animation
from matplotlib.patches import Ellipse
import numpy as np
plt.rcParams["figure.figsize"] = [7.00, 3.50]
plt.rcParams["figure.autolayout"] = True
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
fig = plt.Figure(dpi=100)
ax = fig.add_subplot(xlim=(0, 2), ylim=(-1, 1))
line, = ax.plot([], [], lw=2)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
def StopAnimation():
anim.pause()
plotEllipse()
def StartAnimation():
anim.resume()
buttonStop = tkinter.Button(master=root, text="Stop Animation", command=StopAnimation)
buttonStop.pack(side=tkinter.BOTTOM)
buttonStart = tkinter.Button(master=root, text="Start Animation", command=StartAnimation)
buttonStart.pack(side=tkinter.BOTTOM)
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
def plotEllipse():
ell= Ellipse((1,0), 1, 0.75, angle=45, edgecolor = 'red', facecolor='none', lw = 2)
fig1, ax1 = plt.subplots(1, 1, figsize=(10, 6))
ax1.add_artist(ell)
ax1.set_xlim(0, 2)
ax1.set_ylim(-1, 1)
plt.show()
def init():
line.set_data([], [])
return line,
def animate(i):
x = np.linspace(0, 2, 1000)
y = np.sin(2 * np.pi * (x - 0.01 * i))
line.set_data(x, y)
return line,
anim = animation.FuncAnimation(fig, animate, init_func=init,frames=200, interval=20, blit=True)
tkinter.mainloop()
i would have expected to plot into the tkinter gui figure with ax.add_artist(ell)
I am trying to plot a figure that has many lines where each line represents a specifc temperature!
An example of what I want is here:
However, I bulit the following code:
x=pd.DataFrame(df1, columns =[0])
J = set(x.iloc[:,0])
print ('Length Temperature',len(J))
O = len(J)
M = len(df1.index)
print('Indexxxxx: ',df1.iloc[0:12+0,5])
for i in range(0,M,O):
figure3 = plt.Figure(figsize=(8, 6), dpi=80)
ax1 = figure3.add_subplot(111)
ax1.scatter(df1.iloc[i+1:M+i,5],df1.iloc[i+1:M+i,6], label = "Temperature " + str((df1.iloc[i, 0])))
scatter1 = FigureCanvasTkAgg(figure3, GraphWindow)
scatter1.get_tk_widget().pack(side=tk.LEFT, fill=tk.BOTH)
ax1.set_xlabel('Reduced Frequency [Hz]')
ax1.set_ylabel('Complex Shear Modulus G*')
ax1.set_yscale('log')
ax1.set_xscale('log')
ax1.set_title('MasterCurve ')
ax1.set_facecolor('whitesmoke')
figure3.patch.set_facecolor('whitesmoke')
ax1.spines['bottom'].set_color('black')
ax1.spines['top'].set_color('black')
ax1.spines['left'].set_color('black')
ax1.spines['right'].set_color('black')
toobar = NavigationToolbar2Tk(scatter1, GraphWindow)
ax1.legend(['(Temperature)' + str((df1.iloc[i, 0]))])
hold(True)
Everything is fine in this code but I am obtaining the lines in blue and the legend is the same for all of them.. This is what I obtained:
My question is, how can I change the color of each line and add new legend in each iteration in the above for loop.
Thanks in advance!
You want a single Figure, a single Axes and a single Canvas, and plot the different curves inside of them. In other words, you do too much inside the cycle…
import tkinter
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import numpy as np
# frequencies and a poor man's dataframe
freq = np.logspace(-1.5, 1.5, 12)
G = {'ABCDE'[n-1]:np.logspace(n-4, n-2-n/4, 12) for n in range(1, 6)}
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
fig = Figure(figsize=(6, 4), dpi=100, layout='constrained')
ax = fig.add_subplot()
# this is our loop on the different curves
lines = [ax.plot(freq, G[letter], '-o', label=letter)[0] for letter in G]
# titles, log axes, legend
ax.set_xlabel('Reduced Frequency [Hz]')
ax.set_ylabel('Complex Shear Modulus G*')
ax.set_yscale('log')
ax.set_xscale('log')
ax.set_title('MasterCurve ')
ax.set_facecolor('whitesmoke')
ax.legend()
connect the figure to a tkinter Canvas --- once
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
# boilerplate
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
toolbar.update()
canvas.mpl_connect("key_press_event", key_press_handler)
button_quit = tkinter.Button(master=root, text="Quit", command=root.destroy)
button_quit.pack(side=tkinter.BOTTOM)
toolbar.pack(side=tkinter.BOTTOM, fill=tkinter.X)
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True)
# let's show our curves
tkinter.mainloop()
I am trying to create a interactive 3d plot that can be rotated, I was able to do this using the following, however I am unable to reset the orientation when I click on the home button. I tried all the answers posted before me none seem to work.
matplolib version - 3.2.2
import tkinter as tk
from tkinter import ttk
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
np.random.seed(1968001)
def randrange(n, vmin, vmax):
'''
Helper function to make an array of random numbers having shape (n, )
with each number distributed Uniform(vmin, vmax).
'''
return (vmax - vmin) * np.random.rand(n) + vmin
fig = Figure()
# Unable to have rotation otherwise so defined this function
def func():
ax = fig.add_subplot(111, projection='3d')
n = 100
# For each set of style and range settings, plot n random points in the box
# defined by x in [23, 32], y in [0, 100], z in [zlow, zhigh].
for m, zlow, zhigh in [('o', -50, -25), ('^', -30, -5)]:
xs = randrange(n, 23, 32)
ys = randrange(n, 0, 100)
zs = randrange(n, zlow, zhigh)
ax.scatter(xs, ys, zs, marker=m)
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
top = tk.Tk()
top_frame = ttk.Frame(top)
top_frame.pack()
canv = FigureCanvasTkAgg(fig, master=top_frame)
canv.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
func()
canv.draw()
toolbar = NavigationToolbar2Tk(canv, top_frame)
toolbar.update()
top.mainloop()
By home button this is what I mean
click here
Edit 1: I've changed the plt.figure to figure.Figure as pointed out by Henry Yik, but still, it's not working
I would like to make an equivalent of the function FuncAnimation from matplotlib.animation, in which I could control the current plotted data using the scrollbar.
Say you have a data array which contains data points to be plotted at each time i. When using FuncAnimation, you first need to define a function ( here animate(i) ) which will be called for each time i = 1 to len(data[:,0]) :
def animate(i):
ax.plot(data[i,:])
anim = FuncAnimation(fig, animate, interval=100, frames=len(data[:,0]))
plt.draw()
plt.show()
but you cannot control the time i, like with a play/stop functionality. What I would like to do is to call the function animate(i), with i being the position of the scrollbar.
I found this example ( using the events from matplotlib:
https://matplotlib.org/3.2.1/users/event_handling.html )
but the mpl_connect doesn't have a "scrollbar_event".
import tkinter
from random import randint
import matplotlib as plt
import numpy as np
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
#create figure
fig = Figure(figsize=(5, 4), dpi=100)
ax = fig.add_axes([0,0,1,1])
ax.imshow(np.array([[0,10],[23,40]]))
#create canvas with figure
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
def on_key_press(event):
ax.clear()
ax.imshow(np.array([[randint(0,30),randint(0,30)],[randint(0,30),randint(0,30)]]))
canvas.draw_idle()
key_press_handler(event, canvas)
print("you pressed {}".format(event.key))
#connect canvas to event function
canvas.mpl_connect("key_press_event", on_key_press)
def _quit():
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
button = tkinter.Button(master=root, text="Quit", command=_quit)
button.pack(side=tkinter.BOTTOM)
tkinter.mainloop()
Actually the scroll functionality is given by matplotlib widgets !!
The example below works fine :
import matplotlib
import tkinter as Tk
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from myPytHeader import *
matplotlib.use('TkAgg')
root = Tk.Tk()
root.wm_title("Embedding in TK")
fig = plt.Figure(figsize=(8, 6))
canvas = FigureCanvasTkAgg(fig, root)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
nDt = nbLines("grid.dat")
nDx = nbGridPoints("grid.dat")
grid = np.zeros( (nDt,nDx) ) ; loadData("grid.dat", grid)
valu = np.zeros( (nDt,nDx) ) ; loadData("valu.dat", valu)
ax=fig.add_subplot(111)
fig.subplots_adjust(bottom=0.25)
ax_time = fig.add_axes([0.12, 0.1, 0.78, 0.03])
s_time = Slider(ax_time, 'Time', 0, nDt, valinit=0, valstep=1)
def update(val):
frame = int(s_time.val)
ax.clear()
ax.set(xlim=(-0.05, 1.05), ylim=(-0.05, 1.25))
ax.grid()
ax.scatter(grid[frame,:], valu[frame,:], color='b', marker='.')
fig.canvas.draw_idle()
s_time.on_changed(update)
Tk.mainloop()
After all these years I've found solutions to my problems here, I am in debt !!!
Here is the final solution I came with.
I hope it can be useful to someone someday somehow !!
import matplotlib
import numpy as np
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.widgets import Slider
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# load data
nDt = 1000
nDx = 400
grd = np.zeros( (nDt,nDx) )
val = np.zeros( (nDt,nDx) )
for t in np.arange(nDt):
for x in np.arange(nDx):
grd[t,x] = x / nDx
val[t,x] = (x / nDx) * (t/nDt) * np.sin(10 * 2*np.pi * (t-x)/nDt)
matplotlib.use('TkAgg')
root = tk.Tk()
root.wm_title("Embedding in TK")
fig = plt.Figure(figsize=(8, 6))
canvas = FigureCanvasTkAgg(fig, root)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
ax=fig.add_subplot(111)
fig.subplots_adjust(bottom=0.25)
ax.set(xlim=(-0.05, 1.05), ylim=(-1.05, 1.05))
ax.grid()
scat = ax.scatter(grd[0,:], val[0,:], color='b', marker='.')
ax_time = fig.add_axes([0.12, 0.1, 0.78, 0.03])
s_time = Slider(ax_time, 'Time', 0, nDt, valinit=0, valstep=1)
i_anim = 0
i_relative = 0
i_current = 0
def updateGraph(i):
y_i = val[i,:]
scat.set_offsets(np.c_[grd[i,:], y_i])
def updateFromAnim(i):
global i_anim
global i_current
global i_relative
i_anim = i
i_current = i + i_relative
s_time.set_val(i_current)
updateGraph(i_current)
def updateFromScroll(val):
global i_anim
global i_current
global i_relative
i_relative = int(s_time.val) - i_anim
i_current = int(s_time.val)
updateGraph(i_current)
def onClick():
global anim_running
if anim_running:
anim.event_source.stop()
anim_running = False
else:
anim.event_source.start()
anim_running = True
start_button = tk.Button(root, text="START/STOP", command=onClick)
start_button.pack()
anim_running = True
anim = FuncAnimation(fig, updateFromAnim, interval=100, frames=nDt)
s_time.on_changed(updateFromScroll)
tk.mainloop()
I wanted to add some comments outside the plots. The code and figures are d below. The problem is that fig2 essentially the same but with the "text1" and "text2" cut off. Can anybody explain why or provide a better solution of adding texts outside the plot? Thanks a lot!
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import Tkinter as Tk
root = Tk.Tk()
x = np.arange(0, 10, 1)
y = np.arange(0, 10, 1)
fig1 = plt.figure()
ax = fig1.add_subplot(111)
ax.plot(x,y)
textx = ax.get_xlim()[0]
texty = ax.get_ylim()[0]
ydist = ax.get_yticks()[1] - ax.get_yticks()[0]
ax.text(textx, texty-2*ydist, "text1")
ax.text(textx, texty-3*ydist, "text2")
fig1.show()
fig2 = plt.Figure()
ax2 = fig2.add_subplot(111)
ax2.plot(x,y)
textx = ax2.get_xlim()[0]
texty = ax2.get_ylim()[0]
ydist = ax2.get_yticks()[1] - ax2.get_yticks()[0]
ax2.text(textx, texty-2*ydist, "text1")
ax2.text(textx, texty-3*ydist, "text2")
canvas = FigureCanvasTkAgg(fig2, master=root)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1.0)
canvas.show()
canvas.draw()
Tk.mainloop()