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()
Related
Newbie here, i am working on a program which allows the users to see the animation of spring extension after adjusting the slider(higher frequency=higher extension). However, based on what i did, the spring will move immediately after the slider is moved. How should i change my code so that the spring will show its extension from 0 to the desired length like a full animation?
from tkinter import *
import math
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.patches as patches
window = Tk()
window.geometry("700x700")
window.configure(bg="white")
fig= Figure(figsize=(3, 3), dpi=100)
pg = fig.add_subplot(111)
def plot_graph():
pg.axis([0,800,-500,1000])
pg.get_yaxis().set_visible(False)
pg.set_title("Mass Spring Simulation ")
pg.set_xlabel("Spring extension (m)")
pg.set_ylabel("Displacement")
pg.grid()
def sine_graph():
pg.clear()
coorx=[]
coory=[]
for x in range(0,round(62.5*frequency)):
coorx.append(x)
y = amplitude*math.sin(x/(frequency))+250
coory.append(y)
rect = patches.Rectangle((coorx[-1], 0), 80,500, color='black')
pg.add_patch(rect)
Line= pg.plot(coorx,coory,label='Spring')
plot_graph()
fig.canvas.draw()
def update_fre(value):
global frequency
frequency = slider2.get()
sine_graph()
amplitude,frequency = 250,1
canvas = FigureCanvasTkAgg(fig, window)
canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
toolbar = NavigationToolbar2Tk(canvas,window)
toolbar.update()
L2 = Label(window,text="Force")
L2.pack()
slider2 = Scale(window,from_=1,to=10,orient=HORIZONTAL,command=update_fre)
slider2.pack()
sc= Label(window,text="Spring Constant = 0.0143N/m")
sc.pack()
sine_graph()
window.mainloop()
what i have tried (clearing the page and redrawing),but my computer will crash automatically when adjusting slider:
def sine_graph():
pg.clear()
coorx=[]
coory=[]
z=1
for i in range (frequency):
for x in range(1,round(62.5*z)):
pg.clear()
coorx.append(x)
y = amplitude*math.sin(x/(frequency))+250
coory.append(y)
z+=1
I'm writing a script using tkinter and matplotlib for data processing, some parts of the code requires polygon selector to choose a region of interest. However, PolygonSelector fails to detect the motion of cursor.
It should be noted that this issue occurs when the interactive mode of matplotlib figure is on.
Simplified code and result are shown below:
#!/usr/bin/env python3
import matplotlib
matplotlib.use("TkAgg")
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.widgets import PolygonSelector
root = tk.Tk()
def draw():
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion() # interactive mode is on
plt.show()
def onselect(data_input):
print(data_input)
PS = PolygonSelector(ax, onselect)
tk.Button(root, text='draw', command=draw).pack()
root.mainloop()
This is the plot after clicking 'draw' button on tkinter GUI, the starting point of polygon stucks at (0,0), it is expected to move with cursor:
When I call draw() outside of tkinter, PolygonSelector works fine:
def draw():
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion() # interactive mode is on
plt.show()
def onselect(data_input):
print(data_input)
PS = PolygonSelector(ax, onselect)
a = input() # prevent window from closing when execution is done
draw()
The simple solution would be to make sure you make your Polygon Selector a global variable. This will keep the selector visually updating.
#!/usr/bin/env python3
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import PolygonSelector
matplotlib.use("TkAgg")
root = tk.Tk()
ps = None
def draw():
global ps
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion()
plt.show()
ps = PolygonSelector(ax, on_select)
def on_select(data_input):
print(data_input)
tk.Button(root, text='draw', command=draw).pack()
root.mainloop()
If you build this into a class then you can avoid the use of global and get the behavior you want by apply the Polygon Selector as a class attribute.
#!/usr/bin/env python3
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import PolygonSelector
matplotlib.use("TkAgg")
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.ps = None
tk.Button(self, text='draw', command=self.draw).pack()
def draw(self):
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion()
plt.show()
self.ps = PolygonSelector(ax, self.on_select)
def on_select(self, data_input):
print(data_input)
if __name__ == "__main__":
GUI().mainloop()
Results:
I have the following code:
from tkinter import *
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class tkPlot:
def __init__(self,master):
frame = Frame(master)
frame.pack()
canvasSubFrame=Frame(frame)
canvasSubFrame.grid(row=0, column=0)
f = Figure(figsize=(8,8), dpi=100)
subplotA = f.add_subplot(211)
subplotA.set_title("Plot A")
subplotB = f.add_subplot(212)
subplotB.set_title("Plot B")
subplotA.plot([1,2,3,4,5,6],[1,4,9,16,25,36])
subplotB.plot([1,2,3,4,5,6],[1,1/2,1/3,1/4,1/5,1/6])
canvas = FigureCanvasTkAgg(f, master=canvasSubFrame)
canvas.draw()
canvas.get_tk_widget().pack(expand=False)
if __name__ == "__main__":
root = Tk()
app = tkPlot(root)
root.mainloop()
Ideally, i would like to have a textbox or spin control besides every top and bottom of each subplot to set the y axes min and max scales (y limits)
What is the proper way to do this. It should work for any number of subplots.
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'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.