I'm building a GUI using Tkinter for the first time and have run into a problem updating the data in a Matplotlib Figure using a button in a different frame. Below is some generalized code to show the error I'm getting.
from Tkinter import *
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class testApp():
def app(self):
self.root = Tk()
self.create_frame1()
self.create_frame2()
self.root.mainloop()
def create_frame1(self):
frame1 = Frame(self.root)
frame1.grid(row=0)
(x, y) = self.create_data()
f = plt.Figure(figsize = (5,2), dpi=100)
a = f.add_subplot(111)
lines1, = a.plot(x, y)
f.tight_layout()
canvas = FigureCanvasTkAgg(f, frame1)
canvas.get_tk_widget().grid()
def create_frame2(self):
frame2 = Frame(self.root)
frame2.grid(row=1)
reEval = Button(frame2, text = "Reevaluate", command = lambda: self.reRand()).grid(sticky = "W")
def reRand(self):
(x, y) = self.create_data()
ax = self.root.frame1.lines1
ax.set_data(x, y)
ax.set_xlim(x.min(), x.max())
ax.set_ylim(y.min(), y.max())
self.root.frame1.canvas.draw()
def create_data(self):
y = np.random.uniform(1,10,[25])
x = np.arange(0,25,1)
return (x, y)
if __name__ == "__main__":
test = testApp()
test.app()
When I run this code, I get an error:
AttributeError: frame1
I think my problem stems from how I am referencing the frame containing the figure, itself, so I'm fairly certain this problem is arising from my lack of Tkinter experience. Any help would be greatly appreciated.
This is more to do with using Python classes than Tkinter. All you really need to do is change all frame1 in create_frame1 to be self.frame1. Similarly in reRand, self.root.frame1 becomes self.frame1.
As it is the name "frame1" doesn't exist beyond the end of create_frame1, but if you save it as an attribute of self you can access it later.
Related
I am trying to use tkinter and matplotlib to create an interface to pair with some lab equipment, but right now I am just loading in some old test data. I am trying to add in the NavigationToolbar2Tk navigation bar.
When I run the program the bar pops up properly but every time I click one of the buttons I get the error 'FigureCanvasTkAgg' object has no attribute 'manager'. The funny thing is that all of the buttons except for save will still perform their operations, they just continually spit out the errors. I have tried creating a seperate frame for the navigation box but that hasn't worked.
import tkinter
import matplotlib
matplotlib.use('TkAgg')
from tkinter import Tk
from tkinter import Label as label
from tkinter import Message
from tkinter import Button as button
from tkinter import Canvas as canvas
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import pyplot as plt
from tkinter import Entry as entry
from matplotlib import style
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
import url
dataset = url.open_and_convert_csv("Wednesday_4pm_107_2_Blue.csv")
data = dataset[2:]
x = []
y = []
for row in data:
strain = row[3]
x.append(strain)
stress = row[4]
y.append(stress)
plt.grid(True, which='major', axis='both')
plt.plot(x, y)
figure = plt.gcf()
def tensile_graph():
canv.get_tk_widget().grid(column = 1, row = 1)
def title():
title_text = title_box.get()
title_box.delete(0,len(title_text))
plt.title(title_text)
figure = plt.gcf()
canv = FigureCanvasTkAgg(figure, master=top)
canv.get_tk_widget().grid(column=1, row=1)
def x_ax():
x_ax_text = x_ax_box.get()
x_ax_box.delete(0, len(x_ax_text))
plt.xlabel(x_ax_text)
figure = plt.gcf()
canv = FigureCanvasTkAgg(figure, master=top)
canv.get_tk_widget().grid(column=1, row=1)
def y_ax():
y_ax_text = y_ax_box.get()
y_ax_box.delete(0, len(y_ax_text))
plt.ylabel(y_ax_text)
figure = plt.gcf()
canv = FigureCanvasTkAgg(figure, master=top)
canv.get_tk_widget().grid(column=1, row=1)
top = tkinter.Tk()
top.geometry('1000x600+30+30')
canv = FigureCanvasTkAgg(figure, master=top)
tensile_graph()
options_frame = tkinter.Frame(top)
options_frame.grid(row = 1, column = 0)
title_box = entry(options_frame)
title_box.grid(row = 1, column = 0)
text = title_box.get()
title_button = button(options_frame,text='Apply',command = title)
title_button.grid(row = 1, column = 1)
title_label = label(options_frame,text='Title')
title_label.grid(row = 0, column = 0)
x_axlabel = label(options_frame,text='X Axis Label')
x_axlabel.grid(row = 2, column = 0)
x_ax_box = entry(options_frame)
x_ax_box.grid(row = 3, column = 0)
x_ax_button = button(options_frame, text = 'Apply', command = x_ax)
x_ax_button.grid(row = 3, column = 1)
y_axlabel = label(options_frame,text='Y Axis Label')
y_axlabel.grid(row = 4, column = 0)
y_ax_box = entry(options_frame)
y_ax_box.grid(row = 5, column = 0)
y_ax_button = button(options_frame, text = 'Apply', command = y_ax)
y_ax_button.grid(row = 5, column = 1)
toolbar_frame = tkinter.Frame(top)
toolbar_frame.grid(column = 1, row = 1)
toolbar = NavigationToolbar2Tk(canv,toolbar_frame)
toolbar.update()
canv._tkcanvas.grid(row=1, column=1)
top.mainloop()
I've managed to fix this error. It's actually a bug in NavigationToolbar2Tk class. If you want to fix it you have to modify their code yourself. You only need to change one line of code in the class NavigationToolbar2Tk.
Navigate to class NavigationToolbar2Tk.
Navigate to method def set_cursor(self, cursor).
Change "window = self.canvas.manager.window" to "window = self.window"
You won't see the error again.
I had the same problem and also when I hovered the mouse around the plot I continuously got errors, I found out that I have to downgrade my matplotlib. Here are the versions that solved my problem:
python 3.7.3,
matplotlib 2.2.2
To downgrade your matplotlib version, open the prompt and write:
conda install matplotlib == 2.2.2
Blockquote I've managed to fix this error. It's actually a bug in NavigationToolbar2Tk class. If you want to fix it you have to modify their code yourself. You only need to change one line of code in the class NavigationToolbar2Tk.
Vincent's answer works a charm.
As I cannot add a direct comment to his answer, I'm adding here which file to modify:
<python_installation>\Lib\site-packages\matplotlib\backends\_backend_tk.py
I'm creating a GUI in Tkinter that has an embedded matplotlib graph. I change the contents of the graph based on a couple of Radiobuttons. These Radiobuttons are linked to a function which redefines the data in the graph, and ends with a call to self.canvas.draw(). I used Unable to update Tkinter matplotlib graph with buttons and custom data substantially, and I got it to work perfectly: the graph is updated with the desired new data.
The problem I run into is that (I'm using dates for the x-tick labels) after the redraw function is called, the x-tick labels get rotated back to horizontal, causing them to overlap. Below is a simplified form of my application, which has no dependencies so can be run directly.
import tkinter
from tkinter import *
from tkinter.ttk import *
import csv
import datetime as dt
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class MainApp:
def __init__(self,parent):
self.RightFrame = LabelFrame(parent,text='RightFrame')
self.RightFrame.pack(side='right')
self.plot_fig=plt.figure(figsize=(4,4),dpi=100)
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
self.fig.autofmt_xdate(rotation=30) ### Sets tick rotation Initially
self.ax.fmt_xdata=mdates.DateFormatter('%Y-%m-%d')
self.ax.hold(False)
self.canvas = FigureCanvasTkAgg(self.fig,master=self.RightFrame)
self.canvas.show()
self.canvas.get_tk_widget().pack(side=TOP)
self.plot_radio_frame = LabelFrame(self.RightFrame)
self.plot_radio_frame.pack(side=BOTTOM)
self.plot_time_radio_var = IntVar()
self.plot_time_radio_var.set(7)
modes = [('1 Week',3),('1 Month',4),('1 Year',5),('5 Years',6),('All',0)]
for text,value in modes:
b= Radiobutton(self.plot_radio_frame,text=text,variable=self.plot_time_radio_var,value=value)
b.pack(side=LEFT)
self.plot_time_radio_var.trace('w',self.plotting_function)
def plotting_function(self, varname, elementname, mode):
plot_time = self.plot_time_radio_var.get()
try:
data_hist = [['2016-12-16',116.4700],
['2016-12-19',115.8000],
['2016-12-20',116.7400],
['2016-12-21',116.8000],
['2016-12-22',116.3500],
['2016-12-23',115.5900],
['2016-12-27',116.5200],
['2016-12-28',117.5200],
['2016-12-29',116.4500],
['2016-12-30',116.6500]
]
days = int(plot_time)
spacing = 1
dates = [line[0] for line in data_hist[-days::spacing]]
new_dates = [dt.datetime.strptime(d,'%Y-%m-%d').date() for d in dates]
Y_DATA = [line[1] for line in data_hist[-days::spacing]]
#print(adj_close)
self.curve = self.ax.plot(new_dates,Y_DATA)
self.canvas.draw()
except Exception as e:
print(str(e))
root = Tk()
MainApp(root)
root.mainloop()
I would like to draw a matrix with imshow with tkinter in a gui. The problem is that after further updates, the gui crash. I don't manage to find answer on the web. You could help me please?
The code:
from numpy import *
from Tkinter import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
root = Tk()
f1 = Figure()
canvas = FigureCanvasTkAgg(f1, master=root)
canvas.show()
canvas.get_tk_widget().pack(fill="x")
a = f1.add_subplot(111)
a.get_axes().set_frame_on(True)
ini = [[i] * 100 for i in range(100)]
cax = a.matshow(ini)
while True:
mat = random.randint(0, 2**16-1, (1000, 1000))
cax.set_data(mat)
canvas.draw()
root.mainloop()
Thank you for your suggestion fhdrsdg but doing this way freeze the windows during the exection of redraw which is boring especially when this previous has a lot of stuff to do.
Here is my code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from Tkinter import Button, Label, Text, Checkbutton, Radiobutton, Frame, Tk, Entry, INSERT, StringVar, IntVar, Toplevel, END
from ttk import Notebook, Combobox
from numpy import arange, zeros, array, uint16, empty, divide, random, ravel
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from matplotlib import cm
from matplotlib.image import AxesImage
import time
from threading import Thread
import os
class Image_direct(Thread):
def __init__(self):
Thread.__init__(self)
self.encore = True
def run(self):
"""This loop can be long ~10s"""
while self.encore:
time.sleep(1)
app.cax.set_extent((0, 1023, 1023, 0))
mat = random.randint(0, 2**16-1, (1024, 1024)).astype("uint16")
app.update_camera(mat)
def stop(self):
self.encore = False
class Deu(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.creer_applets()
def creer_applets(self):
self.fen4 = Frame(fen)
self.fen4.pack(side="bottom", fill="both")
self.fen1 = Frame(fen, width=200)
self.fen1.pack(side="left", fill="both", expand=False)
self.note = Notebook(fen, width=1)
self.tab_mat = Frame(self.note)
self.note.add(self.tab_mat, text = "Caméra", compound="top")
self.note.pack(side="left", fill="both", expand=True)
self.fen3 = Frame(fen, width=250)
self.fen3.pack(side="left", fill="both", expand=False)
Button(self.fen4, text="Quit", command=self.aurevoir).pack(fill="x", side="top")
self.interp = StringVar()
self.interp.set("none")
liste_interp = ["none", "nearest", "bilinear", "bicubic", "spline16", "spline36", "hanning", "hamming", "hermite", "kaiser", "quadric", "catrom", "gaussian", "bessel", "mitchell", "sinc", "lanczos"]
self.choix_interp = Combobox(self.tab_mat, textvariable=self.interp, state="readonly", width=10)
self.choix_interp['values'] = liste_interp
self.cmap = StringVar()
self.cmap.set("jet")
palettes = sorted(m for m in cm.datad if not m.endswith("_r"))
self.choix_palette = Combobox(self.tab_mat, textvariable=self.cmap, state="readonly", width=10)
self.choix_palette['values'] = palettes
self.bouton_palette = Button(self.tab_mat, text="Changer la palette", command=self.changer_cmap)
self.f1 = Figure()
self.canvas1 = FigureCanvasTkAgg(self.f1, master=self.tab_mat)
self.canvas1.show()
self.canvas1.get_tk_widget().pack(fill="both", expand=1)
NavigationToolbar2TkAgg(self.canvas1, self.tab_mat)
self.a = self.f1.add_subplot(111)
self.bg = self.canvas1.copy_from_bbox(self.a.bbox)
self.a.get_axes().set_frame_on(True)
ini = random.randint(0, 2**16-1, (1024, 1024))
self.cax = self.a.matshow(ini, cmap=self.cmap.get(), interpolation=self.interp.get(), picker=True, alpha=1.0)
self.a.format_coord = lambda x, y: 'x=%d, y=%d, z=%d' % (x, y, ini[round(y), round(x)])
self.cbar = self.f1.colorbar(self.cax)
self.cbar.set_label("coups")
self.bouton_palette.pack(side="left")
self.choix_interp.pack(side="left")
self.choix_palette.pack(side="left")
Button(self.tab_mat, text=">", command=lambda: self.changer_cbar(1)).pack(side="right")
self.cbar_auto = IntVar()
self.chb3 = Checkbutton(self.tab_mat, text="Auto?", variable=self.cbar_auto, onvalue=1, offvalue=0, indicatoron=0, command=lambda: self.changer_cbar(0))
self.chb3.select()
self.chb3.pack(side="right")
Button(self.tab_mat, text="<", command=lambda: self.changer_cbar(-1)).pack(side="right")
self.bouton_direct_on = Button(self.fen3, width=20, text="Démarrer le direct", command=self.image_direct_on)
self.bouton_direct_on.place(x=0, y=400)
self.bouton_direct_off = Button(self.fen3, width=20, text="Arrêter le direct", command=self.image_direct_off)
self.bouton_direct_off.config(state="disabled")
self.bouton_direct_off.place(x=0, y=430)
def changer_cbar(self, sens):
if sens == -1:
self.cbar.set_clim(vmin=self.cax.get_array().min(), vmax=0.9*self.cbar.get_clim()[1])
elif sens == 0 and self.cbar_auto.get():
self.cbar.set_clim(vmin=self.cax.get_array().min(), vmax=self.cax.get_array().max())
elif sens == 1:
self.cbar.set_clim(vmin=self.cax.get_array().min(), vmax=2*self.cbar.get_clim()[1])
self.cax.set_clim(self.cbar.get_clim())
self.canvas1.restore_region(self.bg)
self.a.draw_artist(self.f1)
self.canvas1.blit(self.f1.bbox)
def changer_cmap(self):
self.cax.set_cmap(self.cmap.get())
self.cax.set_interpolation(self.interp.get())
self.canvas1.draw()
def update_camera(self, mat):
xmin = min([int(i) for i in app.a.get_xlim()])
xmax = max([int(i) for i in app.a.get_xlim()])
ymin = min([int(i) for i in app.a.get_ylim()])
ymax = max([int(i) for i in app.a.get_ylim()])
self.a.format_coord = lambda x, y: 'x=%d, y=%d, z=%d' % (x, y, mat[round(y), round(x)])
self.cax.set_data(mat)
self.changer_cbar(0)
def image_direct_on(self):
self.bouton_direct_off.config(state="normal")
self.bouton_direct_on.config(state="disabled")
self.dire = Image_direct()
self.dire.setDaemon(True)
self.dire.start()
def image_direct_off(self):
self.bouton_direct_off.config(state="disabled")
self.bouton_direct_on.config(state="normal")
self.dire.stop()
del self.dire
def aurevoir(self):
try:
self.dire.isAlive()
except:
pass
else:
self.dire.stop()
fen.quit()
fen.destroy()
if __name__ == '__main__':
fen = Tk()
fen.geometry("1300x750")
app = Deu(fen)
fen.mainloop()
What is strange is that:
the crash occurs when the cursor is on the image
under linux, there is no crash.
It can happen in few seconds or few minutes when the cursor is over the image or when I zoom/dezoom the image. Then the window becomes white, and a pop-up appears with the message "pythonw.exe not responding". Idle says nothing. I am in the complete fog :/
I think the problem is with running the while True loop.
Try to limit the time between each repetition using the after method.
The following code updates the canvas every 100ms
from numpy import *
from Tkinter import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
root = Tk()
f1 = Figure()
canvas = FigureCanvasTkAgg(f1, master=root)
canvas.show()
canvas.get_tk_widget().pack(fill="x")
a = f1.add_subplot(111)
a.get_axes().set_frame_on(True)
ini = [[i] * 100 for i in range(100)]
cax = a.matshow(ini)
def redraw():
mat = random.randint(0, 2**16-1, (1000, 1000))
cax.set_data(mat)
canvas.draw()
root.after(100, redraw)
redraw()
root.mainloop()
I was wondering if anyone had an idea as to why the code below does not display a graph with a line in it after the button on the GUI is pressed. I would like to create a program that executes a long list of commands after a set of data is imported by clicking a button. One of these commands would be to display the spectral data on a graph within the same window. Here is what I have so far:
# import modules that I'm using
import matplotlib
matplotlib.use('TKAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.pyplot as pltlib
import Tkinter
from Tkinter import *
import numpy as np
import scipy as sc
#import matplotlib.pyplot as pltlib
# lmfit is imported becuase parameters are allowed to depend on each other along with bounds, etc.
from lmfit import minimize, Parameters, Minimizer
#Make object for application
class App_Window(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
button = Tkinter.Button(self,text="Open File",command=self.OnButtonClick).pack(side=Tkinter.TOP)
self.canvasFig=pltlib.figure(1)
Fig = matplotlib.figure.Figure(figsize=(5,4),dpi=100)
FigSubPlot = Fig.add_subplot(111)
x=[]
y=[]
self.line1, = FigSubPlot.plot(x,y,'r-')
self.canvas = matplotlib.backends.backend_tkagg.FigureCanvasTkAgg(Fig, master=self)
self.canvas.show()
self.canvas.get_tk_widget().pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
self.canvas._tkcanvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
self.resizable(True,False)
self.update()
def refreshFigure(self,x,y):
self.line1.set_xdata(x)
self.line1.set_ydata(y)
self.canvas.draw()
def OnButtonClick(self):
# file is opened here and some data is taken
# I've just set some arrays here so it will compile alone
x=[]
y=[]
for num in range(0,1000):x.append(num*.001+1)
# just some random function is given here, the real data is a UV-Vis spectrum
for num2 in range(0,1000):y.append(sc.math.sin(num2*.06)+sc.math.e**(num2*.001))
X = np.array(x)
Y = np.array(y)
self.refreshFigure(X,Y)
if __name__ == "__main__":
MainWindow = App_Window(None)
MainWindow.mainloop()
That is because the range of xaxis & yaxis doesn't change to new data's range, change your refreshFigure as following:
def refreshFigure(self,x,y):
self.line1.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()
Dear programmming communauty,
I am trying to perform a "interactive plot" based on Tkinter and pylab.plot in order to plot 1D values. The abssissa are a 1D numpy array x and the ordonates values are in a multidimension array Y, eg.
import numpy
x = numpy.arange(0.0,3.0,0.01)
y = numpy.sin(2*numpy.pi*x)
Y = numpy.vstack((y,y/2))
I want to display y or y/2 (the elements of Y matrix) according to x and change between them with 2 buttons left and right (in order to go to more complex cases). Usually I create some functions like the following to plot graphs.
import pylab
def graphic_plot(n):
fig = pylab.figure(figsize=(8,5))
pylab.plot(x,Y[n,:],'x',markersize=2)
pylab.show()
To add two buttons to change the value of nparameter, I have tried this without success :
import Tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class App:
def __init__(self,master):
# Create a container
frame = Tkinter.Frame(master)
frame.pack()
# Create 2 buttons
self.button_left = Tkinter.Button(frame,text="<",command=self.decrease)
self.button_left.pack(side="left")
self.button_right = Tkinter.Button(frame,text=">",command=self.increase)
self.button_right.pack(side="left")
self.canvas = FigureCanvasTkAgg(fig,master=self)
self.canvas.show()
def decrease(self):
print "Decrease"
def increase(self):
print "Increase"
root = Tkinter.Tk()
app = App(root)
root.mainloop()
Can someone help me to understand how to perform such kind of feature ? Many thanks.
To change the y-values of the line, save the object that's returned when you plot it (line, = ax.plot(...)) and then use line.set_ydata(...). To redraw the plot, use canvas.draw().
As a more complete example based on your 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="< Decrease Slope",
command=self.decrease)
self.button_left.pack(side="left")
self.button_right = Tkinter.Button(frame,text="Increase Slope >",
command=self.increase)
self.button_right.pack(side="left")
fig = Figure()
ax = fig.add_subplot(111)
self.line, = ax.plot(range(10))
self.canvas = FigureCanvasTkAgg(fig,master=master)
self.canvas.show()
self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
frame.pack()
def decrease(self):
x, y = self.line.get_data()
self.line.set_ydata(y - 0.2 * x)
self.canvas.draw()
def increase(self):
x, y = self.line.get_data()
self.line.set_ydata(y + 0.2 * x)
self.canvas.draw()
root = Tkinter.Tk()
app = App(root)
root.mainloop()