Related
If I pan the function in the following example, the axis position is moved in order to keep TEXT in the canvas. Is there a way to keep the axes and to treat TEXT just like the plotted line while keeping constrained_layout=true?
import tkinter as tk
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.backends.backend_tkagg as tkagg
class NewGUI():
def __init__(self):
self.root = tk.Tk()
self.root.geometry('500x400+200+200')
self.plot_frame = tk.Frame()
self.plot_frame.place(x=10, y=10, relheight=0.7, relwidth=0.7, anchor='nw')
self.plot_window = Plotwindow(self, (18,12))
self.root.mainloop()
class Plotwindow():
def __init__(self, root, size):
fig = mpl.figure.Figure(size, constrained_layout=True)
ax = fig.add_subplot(111)
canvas = tkagg.FigureCanvasTkAgg(fig, master=root.plot_frame)
canvas.get_tk_widget().pack()
toolbar = tkagg.NavigationToolbar2Tk(canvas, root.root)
toolbar.update()
x = np.arange(0, 5, 0.1)
y = np.sin(x)
ax.plot(x, y)
ax.text(0, 0, 'TEXT')
if __name__ == '__main__':
new = NewGUI()
There are plenty of web examples (1,2,3,4) and threads (1,2,3) about imbedding a plot into a tkinter window, but very few that address plotting in a separate environment and importing the resulting graph to the tkinter window.
In a nutshell, I have a program that calculates many different values, and exports those values to a separate file that creates a large number of plots. My tkinter application accepts parameters in Entry boxes, before applying them to the main file that does all the calculations. Typically, I would just follow the examples I linked, but with such a large number of plots being generated and the need to be able to select any particular graph I need at a given time, this would be inefficient and time consuming to brute-force. There must be a better way!
Below is a simplified example of how I am trying to accomplish this task:
import tkinter as tk
from matplotlib import pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
import numpy as np
def example_plot(A):
# Plot generated outside of tkinter environment, but controlled by
# variable within tkinter window.
x = np.linspace(0, 10, 50)
y = A*x**2
fig, ax = plt.subplots()
ax.plot(x,y)
ax.set_xlabel('x')
ax.set_ylabel('y')
return fig
window = tk.Tk()
window.geometry('256x256')
variableEntry = tk.Entry(width = 10)
variableLabel = tk.Label(window, text = "A")
variableEntry.grid(row = 0, column = 0)
variableLabel.grid(row = 0, column = 1)
def plotButton():
A = variableEntry.get()
A = int(A)
figure = Figure(figsize = (1,1), dpi = 128)
add = figure.add_subplot(1,1,1)
example = example_plot(A)
add.imshow(example)
canvas = FigureCanvasTkAgg(figure)
canvas.get_tk_widget().grid(row = 2, column = 0)
toolbar = NavigationToolbar2Tk(canvas)
toolbar.update()
canvas._tkcanvas.grid(row = 3 , column = 0)
canvas.show()
applyButton = tk.Button(master = window, text = "Apply", command = plotButton)
applyButton.grid(row = 1,column = 0)
window.mainloop()
When I run this, set A to some integer and press apply, I get an error
TypeError: Image data of dtype object cannot be converted to float
It seems that add.imshow() doesn't like that I fed it the figure. Is there some way to obtain the figure (ie: example = example_plot(A)) and store it to display later?
Try this:
import tkinter as tk
from matplotlib import pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, \
NavigationToolbar2Tk
import numpy as np
def example_plot(A):
# Plot generated outside of tkinter environment, but controlled by
# variable within tkinter window.
x = np.linspace(0, 10, 50)
y = A*x*x # This should run slightly faster
fig, ax = plt.subplots()
ax.plot(x,y)
ax.set_xlabel("x")
ax.set_ylabel("y")
return fig
window = tk.Tk()
frame = tk.Frame(window)
frame.pack()
variable_entry = tk.Entry(frame, width=10)
variable_label = tk.Label(frame, text="A")
variable_entry.pack(side="left", fill="x")
variable_label.pack(side="left")
def plot():
A = int(variable_entry.get())
figure = Figure(figsize=(1, 1), dpi=128)
add = figure.add_subplot(1, 1, 1)
figure = example_plot(A)
canvas = FigureCanvasTkAgg(figure)
canvas.get_tk_widget().pack()
toolbar = NavigationToolbar2Tk(canvas, window)
toolbar.update()
canvas.get_tk_widget().pack()
# canvas.show() # There is no need for this
apply_button = tk.Button(window, text="Apply", command=plot)
apply_button.pack(fill="x")
window.mainloop()
Your example_plot returns a Figure so you can use figure = example_plot(A) and then FigureCanvasTkAgg(figure). I also added a frame and tried to make everything look better.
I am trying to design a python gui to be able to assess impacts of motion by plotting brain slices, the framewise displacement timeseries, and different outputs of motion detection algorithms. I want to be able to slide through each of the brain volumes individually (180 volumes per scan) so that I can compare the FD timecourse to what the actual brain data looks like.
I've been using tkinter and I can plot several slices of one brain volume, but I'm having updating volume that is selected. I've tried creating buttons to advance and go back, and also using a tkinter Scale.
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import os
import nibabel
from nilearn import plotting
from nilearn import image
from matplotlib.widgets import Slider, Button, RadioButtons
data_path = os.getcwd()
file='sub-HV01baseline_task-EA1_bold.nii'
file_path = os.path.join(data_path,file)
EA1=nibabel.load(file_path)
struct_arr2 = EA1.get_data()
vol=1
import tkinter as tk
from tkinter import ttk
fig = plt.Figure(figsize=(10,5), dpi=100)
class App:
def __init__(self, master):
self.event_num = 1
frame = tk.Frame(master)
frame.pack()
self.txt = tk.Entry(frame,width=10)
self.txt.pack(side="bottom")
self.lbl = tk.Label(frame, text="FD Value")
self.lbl.pack(side="bottom")
self.btn = tk.Button(frame, text = "Update",command=self.clicked)
self.btn.pack(side="bottom")
self.txt.focus()
self.var =tk.IntVar(frame)
self.var.set(0)
self.vol_scale=tk.Scale(frame,from_=0, to=180,orient="horizontal",sliderlength=20,command=self.show_slices(fig))
self.increase_btn = tk.Button(frame, text = "Increase",command=self.show_slices(fig))
self.increase_btn.pack(side="bottom")
self.vol_scale.pack(side="bottom")
#self.spin = tk.Spinbox(frame, from_=0, to=180, width=5, textvariable=self.var)
#self.spin.pack(side="bottom")
self.canvas = FigureCanvasTkAgg(fig,master=master)
self.canvas.get_tk_widget().pack(side=tk.TOP)
def clicked(self):
res = "FD = " + self.txt.get()
self.lbl.configure(text = res)
def show_slices(self,fig):
vol = self.vol_scale.get()
slice_0 = struct_arr2[:, :, 10,vol]
slice_1 = struct_arr2[:, : , 15,vol]
slice_2 = struct_arr2[:, :, 20,vol]
slice_3 = struct_arr2[:, :, 25,vol]
slice_4 = struct_arr2[:, : , 30,vol]
slices=[slice_0, slice_1, slice_2, slice_3, slice_4]
axes = fig.subplots(1, len(slices))
#svol = Slider(axes, 'Vol', 0, 180, valinit=0, valstep=1)
fig.subplots_adjust(hspace=0, wspace=0)
for i, slice in enumerate(slices):
axes[i].xaxis.set_major_locator(plt.NullLocator())
axes[i].yaxis.set_major_locator(plt.NullLocator())
axes[i].imshow(slice.T, origin="lower")
root=tk.Tk()
app = App(root)
root.mainloop()
Currently I'm getting an error that "App has no attribute 'vol_scale'" even though I've defined it above.
I am working in a project which needs to plot a graph dynamically as the inputs in a tkinter spinbox is changed.
I have a sample code:
from tkinter import *
from tkinter import font
from tkinter.font import Font
from tkinter import messagebox
print("'Tkinter' module is found as tkinter.")
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
print("Importing matplotlib from libraries.")
master = Tk()
def ok(x_val=1000,y_val=20):
fig = Figure(figsize=(5,5),dpi=70)
ax = fig.subplots()
ax.set_title("Right Ear")
ax.set_ylabel("db HL")
ax.set_xlabel("Frequency")
ax.set_xlim(100,9000)
ax.set_ylim(130,-10)
ax.set_facecolor("#ffd2d2")
x = [125,250,500,1000,2000,4000,8000]
ticks = [125,250,500,"1K","2K","4K","8K"]
xm = [750,1500,3000,6000]
ax.set_xscale('log', basex=2)
ax.set_xticks(x)
ax.set_xticks(xm, minor=True)
ax.set_xticklabels(ticks)
ax.set_xticklabels([""]*len(xm), minor=True)
ax.yaxis.set_ticks([120,110,100,90,80,70,60,50,40,30,20,10,0,-10])
ax.plot([x_val],[y_val],'r+',markersize=15.0,mew=2)
ax.grid(color="grey")
ax.grid(axis="x", which='minor',color="grey", linestyle="--")
canvas = FigureCanvasTkAgg(fig, master=master)
canvas.show()
canvas.get_tk_widget().grid(column=0,row=2,columnspan=3,rowspan=15)
def action():
print(spin.get())
canvas.draw()
ok(spin.get(),10)
spin = Spinbox(master, from_=125,to=8000,command=action)
spin.grid(column=5,row=2)
ok()
This code does not change the plot, I cannot understand how to change it, to be precise, how to use canvas.draw() here to do the work. The spinbox has value range from 125 to 8000, I could not figure out how to take the value of the spinbox every time it changes (can use command= but how to implement) and feed it to the x axis of ax.plot() and plot dynamically. As the value of spinbox changes the plot also changes to the new position and removes the previous plot from the previous position.
You need to make the variables you need available. A usual approach is to use a class and make those class variables. Those can then be accessed from within the class (self) or outside as attributes.
from Tkinter import *
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class PlotClass():
def __init__(self):
fig = Figure(figsize=(5,5),dpi=70)
ax = fig.subplots()
ax.set_title("Right Ear")
ax.set_ylabel("db HL")
ax.set_xlabel("Frequency")
ax.set_xlim(100,9000)
ax.set_ylim(130,-10)
ax.set_facecolor("#ffd2d2")
x = [125,250,500,1000,2000,4000,8000]
ticks = [125,250,500,"1K","2K","4K","8K"]
xm = [750,1500,3000,6000]
ax.set_xscale('log', basex=2)
ax.set_xticks(x)
ax.set_xticks(xm, minor=True)
ax.set_xticklabels(ticks)
ax.set_xticklabels([""]*len(xm), minor=True)
ax.yaxis.set_ticks([120,110,100,90,80,70,60,50,40,30,20,10,0,-10])
self.line, = ax.plot([],[],'r+',markersize=15.0,mew=2)
ax.grid(color="grey")
ax.grid(axis="x", which='minor',color="grey", linestyle="--")
self.canvas = canvas = FigureCanvasTkAgg(fig, master=master)
canvas.show()
canvas.get_tk_widget().grid(column=0,row=2,columnspan=3,rowspan=15)
self.spin = Spinbox(master, from_=125,to=8000,command=self.action)
self.spin.grid(column=5,row=2)
def ok(self, x=1000,y=20):
self.line.set_data([x],[y])
self.canvas.draw_idle()
def action(self):
self.ok(float(self.spin.get()),10)
master = Tk()
plotter = PlotClass()
plotter.ok(125,10)
master.mainloop()
Note: In newer versions of matplotlib you should use NavigationToolbar2Tk instead of NavigationToolbar2TkAgg.
For a project I am working on a simple harmonic motion simulator (How a mass oscillates over time). I have got the data produced correctly and already have a graph produced within a tkinter frame work. At the moment it only shows a static graph where my objective is to display the graph as an animation over time.
So for ease sake I have created a mock up of the programme using the following code:
#---------Imports
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as Tk
from tkinter import ttk
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#---------End of imports
fig, ax = plt.subplots()
x = np.arange(0, 2*np.pi, 0.01) # x-array
line, = ax.plot(x, np.sin(x))
def animate(i):
line.set_ydata(np.sin(x+i/10.0)) # update the data
return line,
ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), interval=25, blit=False)
#plt.show() #What I want the object in tkinter to appear as
root = Tk.Tk()
label = ttk.Label(root,text="SHM Simulation").grid(column=0, row=0)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().grid(column=0,row=1)
Tk.mainloop()
This code will display the animation that I want in the tkinter frame work when the plt.show() is uncommented. I would like to be able to place that animation within the framework of tkinter.
I have also been on the matplotlib website and viewed all of the animation examples and none of them have helped. I have also looked on Embedding an animated matplotlib in tk and that has placed the tkinter button within pyplot figure, whereas I would like to place the figure within a tkinter frame.
So just to clarify, I would like to be able to place the animation produced when plt.show() is uncommented in a tkinter frame, ie root = tk().
I modified your code:
#---------Imports
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as Tk
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#---------End of imports
fig = plt.Figure()
x = np.arange(0, 2*np.pi, 0.01) # x-array
def animate(i):
line.set_ydata(np.sin(x+i/10.0)) # update the data
return line,
root = Tk.Tk()
label = Tk.Label(root,text="SHM Simulation").grid(column=0, row=0)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().grid(column=0,row=1)
ax = fig.add_subplot(111)
line, = ax.plot(x, np.sin(x))
ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), interval=25, blit=False)
Tk.mainloop()
Based on the answer of user151522 that didnt work for me at the first try, i made a few modifications to work in python 3.7:
#---------Imports
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#---------End of imports
from tkinter import Frame,Label,Entry,Button
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def Clear(self):
print("clear")
self.textAmplitude.insert(0, "1.0")
self.textSpeed.insert(0, "1.0")
def Plot(self):
self.v = float(self.textSpeed.get())
self.A = float(self.textAmplitude.get())
def animate(self,i):
self.line.set_ydata(self.A*np.sin(self.x+self.v*i)) # update the data
return self.line,
def init_window(self):
self.master.title("Use Of FuncAnimation in tkinter based GUI")
self.pack(fill='both', expand=1)
#Create the controls, note use of grid
self.labelSpeed = Label(self,text="Speed (km/Hr)",width=12)
self.labelSpeed.grid(row=0,column=1)
self.labelAmplitude = Label(self,text="Amplitude",width=12)
self.labelAmplitude.grid(row=0,column=2)
self.textSpeed = Entry(self,width=12)
self.textSpeed.grid(row=1,column=1)
self.textAmplitude = Entry(self,width=12)
self.textAmplitude.grid(row=1,column=2)
self.textAmplitude.insert(0, "1.0")
self.textSpeed.insert(0, "1.0")
self.v = 1.0
self.A = 1.0
self.buttonPlot = Button(self,text="Plot",command=self.Plot,width=12)
self.buttonPlot.grid(row=2,column=1)
self.buttonClear = Button(self,text="Clear",command=self.Clear,width=12)
self.buttonClear.grid(row=2,column=2)
self.buttonClear.bind(lambda e:self.Clear)
tk.Label(self,text="SHM Simulation").grid(column=0, row=3)
self.fig = plt.Figure()
self.x = 20*np.arange(0, 2*np.pi, 0.01) # x-array
self.ax = self.fig.add_subplot(111)
self.line, = self.ax.plot(self.x, np.sin(self.x))
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.get_tk_widget().grid(column=0,row=4)
self.ani = animation.FuncAnimation(self.fig, self.animate, np.arange(1, 200), interval=25, blit=False)
root = tk.Tk()
root.geometry("700x400")
app = Window(root)
tk.mainloop()
This answer will hopefully be allowed. It is an answer to what I was actually interested in, when I initially found this question, that is, 'Embedding a Matplotlib animation into a tkinter based GUI'.
The code that gave the previous screenshot has been extended, in this code the canvas has been placed inside a class definition, together with some code for two command buttons, these buttons don't actually do "anything" but the structure is there for possible further development.
The following screenshot was produced with the aid of the extended code
A screenshot of the SHM animation running from within a tkinter based GUI
The extended code used for the above screenshot is given below.
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
from tkinter import Frame,Label,Entry,Button
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def Clear(self):
x=0
# def Plot(self):
# x=0
def init_window(self):
def animate(i):
self.line.set_ydata(np.sin(self.x+i/10.0)) # update the data
return self.line,
self.master.title("Use Of FuncAnimation in tkinter based GUI")
self.pack(fill='both', expand=1)
#Create the controls, note use of grid
self.labelSpeed = Label(self,text="Speed (km/Hr)",width=12)
self.labelSpeed.grid(row=0,column=1)
self.labelAmplitude = Label(self,text="Amplitude",width=12)
self.labelAmplitude.grid(row=0,column=2)
self.textSpeed = Entry(self,width=12)
self.textSpeed.grid(row=1,column=1)
self.textAmplitude = Entry(self,width=12)
self.textAmplitude.grid(row=1,column=2)
# self.buttonPlot = Button(self,text="Plot",command=self.Plot,width=12)
self.buttonPlot = Button(self,text="Plot",width=12)
self.buttonPlot.grid(row=2,column=1)
self.buttonClear = Button(self,text="Clear",command=self.Clear,width=12)
self.buttonClear.grid(row=2,column=2)
# self.buttonClear.bind(lambda e:self.Plot)
self.buttonClear.bind(lambda e:self.Clear)
tk.Label(self,text="SHM Simulation").grid(column=0, row=3)
self.fig = plt.Figure()
self.x = np.arange(0, 2*np.pi, 0.01) # x-array
self.ax = self.fig.add_subplot(111)
self.line, = self.ax.plot(self.x, np.sin(self.x))
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.get_tk_widget().grid(column=0,row=4)
self.ani = animation.FuncAnimation(self.fig, animate, np.arange(1, 200), interval=25, blit=False)
root = tk.Tk()
root.geometry("700x400")
app = Window(root)
tk.mainloop()