I would like to have a GUI where one part of the interface contains a plot and the rest of the window some tools to work around the plot.
I would like to use mpl_connect to connect the matplotlib canvas with the tkk frame so that I can choose points in the plot to work with.
This was my try, which cowardly refuses to do what I think it should do:
import Tkinter as tk
import ttk
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2TkAgg)
import matplotlib.pyplot as plt
import numpy as np
class Frame_examples_program():
def __init__(self):
self.window = tk.Tk()
self.window.title("Amazing GUI 5000")
self.create_widgets()
def create_widgets(self):
self.window['padx'] = 10
self.window['pady'] = 10
# - - - - - - - - - - - - - - - - - - - - -
# Frame
frame1 = ttk.Frame(self.window, relief=tk.RIDGE)
frame1.grid(row=0, column=0, sticky=tk.E + tk.W + tk.N + tk.S, padx=0, pady=0)
frame2 = ttk.Frame(self.window, relief=tk.RIDGE)
frame2.grid(row=1, column=0, sticky=tk.E + tk.W + tk.N + tk.S, padx=0, pady=0)
self.PlotFrame(frame1, frame2)
class PlotFrame():
# The plot
def __init__(self, parent1, parent2):
self.parent1 = parent1
self.parent2 = parent2
canvas = self.plot()
self.plot_toolbar(canvas)
def plot(self):
# the actual plot
fig, ax = plt.subplots()
plt.imshow(np.ones((100,100)),picker=True)
canvas = FigureCanvasTkAgg(fig, self.parent1)
canvas.mpl_connect('button_press_event', self.onclick)
return(canvas)
def plot_toolbar(self, canvas):
# the tool bar to the plot
toolbar = NavigationToolbar2TkAgg(canvas, self.parent2)
toolbar.update()
canvas.get_tk_widget().grid(row=1, column=1)
canvas.draw()
def onclick(self, event):
# the devilish thing that does nothing!
print('WOHOOOO')
# Create the entire GUI program
program = Frame_examples_program()
# Start the GUI event loop
program.window.mainloop()
As you'll see when you run this, the matplotlib toolbar nicely works, but I just can't call the onclick event! Why?
The PlotFrame instance that is created via self.PlotFrame(frame1, frame2) is not stored anywhere and hence garbage collected. At the point where you expect the callback to happen this instance does not exist in memory anymore.
Solution: Make sure to keep a reference to the PlotFrame at all time, e.g.
self.myplot = self.PlotFrame(frame1, frame2)
Note that this is a more or less general rule: You would almost never instantiate a class without storing it anywhere. In case you do and don't run into trouble, that would mostly be sign that the class is not needed at all.
Related
Can somebody help me out with threading in python and getting a progress bar working?
Even research gives quite a lot results, i cannot get it working.
I never did threading before and i cannot tell where to put things correctly.
For testing purposes i prepared a simple GUI with a button and a progress bar:
After clicking the button a simple 3d plot will pop-up.
Such plotting may take some computation time and while a user need to wait, id like to have the GUI not frozen and the progressbar animated.
At the moment the GUI freezes until the plot will show up. And after that the progress bar start the animation.
I have read a lot about threading and to put calculations and gui to different threads?
But im just to noobish to get it working.
Is somebody able to explain me more, direct me to similar problems or documentations?
Or maybe, in case is quickly solved, overtake the simple code and correct it the way it should be?
Thanks in advance for any kind of help.
Python script so far:
from time import sleep
from tkinter import EW
import ttkbootstrap as ttk
import numpy as np
import matplotlib.pyplot as plt
def main():
def plot_sample():
sleep(5) # simulate calculation times
x = np.outer(np.linspace(-2, 2, 30), np.ones(30))
y = x.copy().T # transpose
z = np.cos(x ** 2 + y ** 2)
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_surface(x, y, z,cmap='viridis', edgecolor='none')
ax.set_title('Surface plot')
plt.show()
def progressbar_start():
progressbar.grid(row=1, column=0, sticky=EW, padx=10, pady=10) # let progressbar appear in GUI
progressbar.start(interval=10)
def progressbar_stop():
progressbar.stop()
progressbar.grid_forget() # hide progressbar when not needed
def button_clicked():
progressbar_start() # start progressbar before computation begins
plot_sample() # plotting
progressbar_stop() # stop progressbar after plot is done
# GUI
# window size and settings
root = ttk.Window()
# Basic settings for the main window
root.title('Test progressbar')
root.minsize(300, 200)
root.resizable(True, True)
root.configure(bg='white')
# grid settings for the main window in which LabelFrames are sitting in
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.rowconfigure(1, weight=1)
# Button fto show 3d-plot
button_calc_3dplot = ttk.Button(root, text='Calculate 3D Plot', command=button_clicked)
button_calc_3dplot.grid(row=0, column=0, padx=5, pady=5)
progressbar = ttk.Progressbar(style='success-striped', mode='indeterminate')
# end of GUI
root.mainloop()
if __name__ == "__main__":
main()
I have one solution. The main problem is, that You can't run the plt.show() function in a Thread. From this follows, that you can't run the window and the plt.show function at the same time.
from time import sleep
from tkinter import EW
import ttkbootstrap as ttk
import numpy as np
import matplotlib.pyplot as plt
from threading import Thread
x, y, z = None, None, None
def main():
def calculate_xyz():
sleep(5) # simulate calculation times
global x, y, z
x = np.outer(np.linspace(-2, 2, 30), np.ones(30))
y = x.copy().T # transpose
z = np.cos(x ** 2 + y ** 2)
def progressbar_start():
progressbar.grid(row=1, column=0, sticky=EW, padx=10, pady=10) # let progressbar appear in GUI
progressbar.start(interval=10)
def progressbar_stop():
progressbar.stop()
progressbar.grid_forget() # hide progressbar when not needed
def button_clicked():
progressbar_start() # start progressbar before computation begins
calculate_xyz_thread = Thread(target=calculate_xyz)
calculate_xyz_thread.start() # calculate x, y and z
root.after(500, wait_for_plotting) # wait for plotting and stop animation after finishing
def wait_for_plotting():
if x is None or y is None or z is None:
root.after(500, wait_for_plotting) # run self again
return # wait for calculation
# calculation is finished
progressbar_stop() # stop progressbar "after" plot is done
ax = plt.axes(projection='3d')
ax.plot_surface(x, y, z, cmap='viridis', edgecolor='none')
ax.set_title('Surface plot')
root.after(500, plot_3d)
def plot_3d():
plt.show()
# GUI
# window size and settings
root = ttk.Window()
# Basic settings for the main window
root.title('Test progressbar')
root.minsize(300, 200)
root.resizable(True, True)
root.configure(bg='white')
# grid settings for the main window in which LabelFrames are sitting in
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.rowconfigure(1, weight=1)
# Button fto show 3d-plot
button_calc_3dplot = ttk.Button(root, text='Calculate 3D Plot', command=button_clicked)
button_calc_3dplot.grid(row=0, column=0, padx=5, pady=5)
progressbar = ttk.Progressbar(style='success-striped', mode='indeterminate')
# end of GUI
root.mainloop()
if __name__ == "__main__":
main()
I am currently working to create a tkinter GUI which shows a matplotlib figure with two sliders. The sliders are to be used for selecting a range of data (they are connected to vertical lines on the plot).
The issue I am running into is that when I change the sliders quickly a few times the GUI freezes. I believe this is due to the on_changed() call I am using to make the sliders update. When you move the slider quickly I believe on_changed() is being called multiple times at a faster rate than the program can keep up with, thus causing the freeze.
Is there any way to add a delay to the on_changed() call so it only looks to see if the slider has been changed every given interval of time (ex. 100 ms)?
Below is example code I found which freezes in the way I described.
import tkinter as tk
from tkinter.ttk import Notebook
from tkinter import Canvas
from tkinter import messagebox as msg
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.widgets import Slider, Button, RadioButtons
class LukeOutline(tk.Tk):
def __init__(self):
# Inherit from tk.Tk
super().__init__()
# Title and size of the window
self.title('Luke Outline')
self.geometry('600x400')
# Create the drop down menus
self.menu = tk.Menu(self,bg='lightgrey',fg='black')
self.file_menu = tk.Menu(self.menu,tearoff=0,bg='lightgrey',fg='black')
self.file_menu.add_command(label='Add Project',command=self.unfinished)
self.menu.add_cascade(label='File',menu=self.file_menu)
self.config(menu=self.menu)
# Create the tabs (Graph, File Explorer, etc.)
self.notebook = Notebook(self)
graph_tab = tk.Frame(self.notebook)
file_explorer_tab = tk.Frame(self.notebook)
# Sets the Graph Tab as a Canvas where figures, images, etc. can be added
self.graph_tab = tk.Canvas(graph_tab)
self.graph_tab.pack(side=tk.TOP, expand=1)
# Sets the file explorer tab as a text box (change later)
self.file_explorer_tab = tk.Text(file_explorer_tab,bg='white',fg='black')
self.file_explorer_tab.pack(side=tk.TOP, expand=1)
# Add the tabs to the GUI
self.notebook.add(graph_tab, text='Graph')
self.notebook.add(file_explorer_tab, text='Files')
self.notebook.pack(fill=tk.BOTH, expand=1)
# Add the graph to the graph tab
self.fig = Figure()
graph = FigureCanvasTkAgg(self.fig,self.graph_tab)
graph.get_tk_widget().pack(side='top',fill='both',expand=True)
EllipseSlider(self.fig)
#------------------------------------------------------
def quit(self):
'''
Quit the program
'''
self.destroy()
#------------------------------------------------------
def unfinished(self):
'''
Messagebox for unfinished items
'''
msg.showinfo('Unfinished','This feature has not been finished')
#------------------------------------------------------
def random_graph(self):
x = list(range(0,10))
y = [i**3 for i in x]
fig = Figure()
axes = fig.add_subplot(111)
axes.plot(x,y,label=r'$x^3$')
axes.legend()
return fig
#----------------------------------------------------------
class EllipseSlider():
#------------------------------------------------------
def __init__(self,fig):
self.fig = fig
# Initial values
self.u = 0. #x-position of the center
self.v = 0. #y-position of the center
self.a = 2. #radius on the x-axis
self.b = 1.5 #radius on the y-axis
# Points to plot against
self.t = np.linspace(0, 2*np.pi, 100)
# Set up figure with centered axes and grid
self.ax = self.fig.add_subplot(111)
self.ax.set_aspect(aspect='equal')
self.ax.spines['left'].set_position('center')
self.ax.spines['bottom'].set_position('center')
self.ax.spines['right'].set_color('none')
self.ax.spines['top'].set_color('none')
self.ax.xaxis.set_ticks_position('bottom')
self.ax.yaxis.set_ticks_position('left')
self.ax.set_xlim(-2,2)
self.ax.set_ylim(-2,2)
self.ax.grid(color='lightgray',linestyle='--')
# Initial plot
self.l, = self.ax.plot(self.u+self.a*np.cos(self.t),
self.v+self.b*np.sin(self.t),'k')
# Slider setup
self.axcolor = 'lightgoldenrodyellow'
self.axb = self.fig.add_axes([0.25, 0.1, 0.65, 0.03], facecolor=self.axcolor)
self.axa = self.fig.add_axes([0.25, 0.15, 0.65, 0.03], facecolor=self.axcolor)
self.sb = Slider(self.axb, 'Y Radius', 0.1, 2.0, valinit=self.b)
self.sa = Slider(self.axa, 'X Radius', 0.1, 2.0, valinit=self.a)
# Call update as slider is changed
self.sb.on_changed(self.update)
self.sa.on_changed(self.update)
# Reset if reset button is pushed
self.resetax = self.fig.add_axes([0.8,0.025,0.1,0.04])
self.button = Button(self.resetax, 'Reset', color=self.axcolor, hovercolor='0.975')
self.button.on_clicked(self.reset)
# Color button setup
self.rax = self.fig.add_axes([0.025, 0.5, 0.15, 0.15], facecolor=self.axcolor)
self.radio = RadioButtons(self.rax, ('red', 'blue', 'green'), active=0)
self.radio.on_clicked(self.colorfunc)
#------------------------------------------------------
def update(self, val):
'''
Updates the plot as sliders are moved
'''
self.a = self.sa.val
self.b = self.sb.val
self.l.set_xdata(self.u+self.a*np.cos(self.t))
self.l.set_ydata(self.u+self.b*np.sin(self.t))
#------------------------------------------------------
def reset(self, event):
'''
Resets everything if reset button clicked
'''
self.sb.reset()
self.sa.reset()
#------------------------------------------------------
def colorfunc(self, label):
'''
Changes color of the plot when button clicked
'''
self.l.set_color(label)
self.fig.canvas.draw_idle()
#----------------------------------------------------------
if __name__ == '__main__':
luke_gui = LukeOutline()
luke_gui.mainloop()
es = EllipseSlider()
(UPDATE):
I have implemented the correction you showed and the code looks to work as intended except the vertical sliders I am using now do not update their position until I move the slider a second time. Please see the section from my code below:
# Call update if slider is changed
self.sa.on_changed(self.schedule_update)
self.sb.on_changed(self.schedule_update)
def update(self, value):
print(value)
self.vert_a.set_xdata(value)
self.vert_b.set_xdata(self.sb.val)
root.update()
def schedule_update(self, new_value):
if self.after_id:
root.after_cancel(self.after_id)
self.after_id = root.after(1000, self.update, new_value)
root = tk.Tk()
mainapp = MainApp(root)
root.mainloop()
Tkinter has a method named after for delaying the execution of a command. Your slider can use this to delay the effect of the slider for a second or two. If the slider moves, it can cancel the previously scheduled command and submit a new one.
Here's an example using plain tkinter for simplicity. Run the code and then move the slider quickly or slowly. You'll see that the label will only update after you haven't moved the slider for a full second.
import tkinter as tk
after_id = None
def schedule_update(new_value):
global after_id
if after_id:
root.after_cancel(after_id)
after_id = root.after(1000, update_label, new_value)
def update_label(new_value):
label.configure(text=f"New value: {new_value}")
root = tk.Tk()
label = tk.Label(root, text="", width=20)
scale = tk.Scale(
root, from_=1, to=100,
orient="horizontal", command=schedule_update
)
label.pack(side="top", fill="x", padx=20, pady=(10,0))
scale.pack(side="bottom", fill="x", padx=20, pady=10)
root.mainloop()
Im using function plot_graph() which is triggered everytime you open Graph window, my question is: How to plot graph with this function everytime on same place?
plot_graph function looks like this:
def plot_graph(self,event):
df_1 = self.controller.df_1
df_2 = self.controller.df_2
df_3 = self.controller.df_3
df_4 = self.controller.df_4
f = Figure(figsize=(5, 5), dpi=100)
a = f.add_subplot(111)
a.plot(df_1['mean'])
a.plot(df_2['mean'])
a.plot(df_3['mean'])
a.plot(df_4['mean'])
a.legend(['bsl morning', '1st expo', 'bsl noon', '2nd expo'])
canvas = FigureCanvasTkAgg(f, self)
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
And im calling this function with self.bind("<<ShowGraph>>", self.plot_graph)
After second call of this function program starts create second graph under first one, on and on. Output of program, as you can see on image.I want to prevent this and have only one graph.
Thank you for help!
I believe you have two choices:
destroy the canvas before creating a new one. See Using tkinter -- How to clear FigureCanvasTkAgg object if exists or similar?
(I think this is a better method) create the figure/canvas in the init section, and reuse those objects to plot new data:
The following demonstrates option #2:
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter
import numpy as np
class MyClass:
def __init__(self, frame):
self.frame = frame
self.fig = Figure(figsize=(5, 5), dpi=100)
self.ax = self.fig.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.fig, self.frame)
self.canvas.draw()
self.canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True)
self.button = tkinter.Button(self.frame, text="plot", command=self.plot_graph)
self.button.pack()
def plot_graph(self):
x, y = np.random.random(size=(2, 10))
self.ax.cla()
self.ax.plot(x, y)
self.canvas.draw()
root = tkinter.Tk()
MyFrame = tkinter.Frame(root)
MyClass(MyFrame)
MyFrame.pack()
root.mainloop()
I've got some issues about blitting a matplotlib plot, which is itself embedded in a Tkinter GUI - the whole program will eventually run on a Raspberry Pi. The question involves various levels, this is my first question, so sorry in advance for any unclarities.
In few words, what I'm doing is this: I'm working on a Tk GUI to read out a number of sensors simultaneously and I'd like to have some real-time updating of the sensor data on said GUI.
I'd like to have each measurable quantity on a separate frame, which is why I decided to set up a class for each Sensor. One of the sensors is a Flow Sensor, which is read out and plotted as follows:
import Tkinter as Tk
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from Backend import Backend #self written data acquisition handler
#global variables
t0 = datetime.now() #start time of program
#Returns time difference in seconds
def time_difference(t1, t2):
delta = t2-t1
delta = delta.total_seconds()
return delta
# Define Class for Flow data display
class FlowFig():
def __init__(self, master): #master:Parent frame for plot
#Initialize plot data
self.t = []
self.Flow = []
#Initialize plot for FlowFig
self.fig = plt.figure(figsize=(4,4))
self.ax = self.fig.add_subplot(111)
self.ax.set_title("Flow Control Monitor")
self.ax.set_xlabel("Time")
self.ax.set_ylabel("Flow")
self.ax.axis([0,100,0,5])
self.line = self.ax.plot(self.t, self.Flow, '-')
#Set up canvas
self.canvas = FigureCanvasTkAgg(self.fig, master = master)
self.canvas.show()
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.ax.grid(True)
# Initialize handler for data aqcuisition
self.Data= Backend()
self.Data.initialize()
#update figure
def update(self):
# get new data values
self.t.append(time_difference(t0, datetime.now()))
Flow,_,_ = self.Data.get_adc_val(1)
self.Flow.append(Flow)
# shorten data vector if too long
if len(self.t) > 200:
del self.t[0]
del self.Flow[0]
#adjust xlims, add new data to plot
self.ax.set_xlim([np.min(self.t), np.max(self.t)])
self.line[0].set_data(self.t, self.Flow)
#blit new data into old frame
self.canvas.restore_region(self.background)
self.ax.draw_artist(self.line[0])
self.canvas.blit(self.ax.bbox)
root.after(25, Flowmonitor.Flowdata.update) #Recursive update
#Flow Frame of GUI
class FlowPage(Tk.Frame):
def __init__(self, parent, controller):
Tk.Frame.__init__(self,parent)
self.parent = parent
self.FlowPlot = FlowFig(self)
self.FlowPlot.canvas.get_tk_widget().grid(row=0, column=0, rowspan=9, columnspan=9)
# Mainloop
root= Tk.Tk()
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
Flowmonitor = FlowPage(root, root)
Flowmonitor.grid(row =0, column=0, rowspan =10, columnspan=10)
Flowmonitor.rowconfigure(0, weight=1)
Flowmonitor.columnconfigure(0, weight=1)
root.after(25, Flowmonitor.FlowPlot.update)
root.mainloop()
What troubles me with my resulting images is this:
When I use the statement copy_from_bbox(self.ax.bbox) I get a graph like this
Obviously, the size of the blitted background doesn't fit the image into which it is blitted. So, I tried to blit the figure's bbox instead (copy_from_bbox(self.fig.bbox)) and got this
Versions of these shifts happen with all combinations of fig.bbox and ax.bbox,
So here come my actual questions:
Can anybody help me find the bug in my above code which causes the missmatch? I'm aware that it is probably very simple yet subtle misstake. It seems very much related to this thread, yet I can't quite glue it together, using bbox.expanded() in the argument of copy_from_bbox() doesn't do much of a difference
.blit() vs. .draw() has already been discussed here.
But since speed is of the essence for my application I think I have to blit. Redrawing the plot gives me framerates of fps=10, whereas blitting runs almost 10x faster. In any case - is there a way to update one of the axes (e.g. time axis) while using blit? (The answer to this is probably closely related to question No.1 )
Lastly, a rather basic question about my application alltogether: Since my sensordata is currently fetched within an infinite, recursive loop - is it possible to run several of such loops in parallel or should I rather go for threading instead, making my code considerably more complex? What are the risks of running infinite, recursive loops? Or should these be avoided in general?
After days of blitting back and forth I'm rather confused about the possibilities regarding ax/fig blitting, so any help regarding the matter is much, much appreciated^^
Please let me know if you need more info about anything, I hope I could illustrate my problem well.
Thanks a lot for your help!
In short: the solution
This was written in Python3, but it should be virtually the exact same in your version of Python2
import tkinter as Tk
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# from Backend import Backend #self written data acquisition handler
import random
#global variables
t0 = datetime.now() #start time of program
#Returns time difference in seconds
def time_difference(t1, t2):
delta = t2-t1
delta = delta.total_seconds()
return delta
# Define Class for Flow data display
class FlowFig():
def __init__(self, master): #master:Parent frame for plot
#Initialize plot data
self.t = []
self.Flow = []
#Initialize plot for FlowFig
self.fig = plt.figure(figsize=(4,4))
self.ax = self.fig.add_subplot(111)
self.ax.set_title("Flow Control Monitor")
self.ax.set_xlabel("Time")
self.ax.set_ylabel("Flow")
self.ax.axis([0,100,0,5])
self.line = self.ax.plot(self.t, self.Flow, '-')
#Set up canvas
self.canvas = FigureCanvasTkAgg(self.fig, master = master)
self.canvas.draw()
self.ax.add_line(self.line[0])
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.ax.grid(True)
# # Initialize handler for data aqcuisition
# self.Data= Backend()
# self.Data.initialize()
#update figure
def update(self):
# get new data values
self.t.append(time_difference(t0, datetime.now()))
Flow = random.uniform(1, 5)
self.Flow.append(Flow)
# shorten data vector if too long
if len(self.t) > 200:
del self.t[0]
del self.Flow[0]
#adjust xlims, add new data to plot
self.ax.set_xlim([np.min(self.t), np.max(self.t)])
self.line[0].set_data(self.t, self.Flow)
#blit new data into old frame
self.canvas.restore_region(self.background)
self.ax.draw_artist(self.line[0])
self.canvas.blit(self.ax.bbox)
self.canvas.flush_events()
root.after(1,self.update)
#Flow Frame of GUI
class FlowPage(Tk.Frame):
def __init__(self, parent, controller):
Tk.Frame.__init__(self,parent)
self.parent = parent
self.FlowPlot = FlowFig(self)
self.FlowPlot.canvas.get_tk_widget().grid(row=0, column=0, rowspan=9, columnspan=9)
# Mainloop
root= Tk.Tk()
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
Flowmonitor = FlowPage(root, root)
Flowmonitor.grid(row =0, column=0, rowspan =10, columnspan=10)
Flowmonitor.rowconfigure(0, weight=1)
Flowmonitor.columnconfigure(0, weight=1)
root.after(25, Flowmonitor.FlowPlot.update)
root.mainloop()
The Changes
Moving to python3
I moved
import Tkinter as Tk
to
import tkinter as Tk
Simulating your adc values
I moved
from Backend import Backend
to
import random
Because I don't have access to the Backend, I just used a random number generator (obviously it isn't the best example of ADC readings, but it is good enough for a tester)
Setting up the canvas
I moved
self.canvas = FigureCanvasTkAgg(self.fig, master = master)
self.canvas.show()
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.ax.grid(True)
to
self.canvas = FigureCanvasTkAgg(self.fig, master = master)
self.canvas.draw()
self.ax.add_line(self.line[0])
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.ax.grid(True)
You must first draw() the canvas, because otherwise matplotlib will throw an error AttributeError: draw_artist can only be used after an initial draw which caches the renderer.
Then, we now add the self.line, with no values in it. Currently it is just an empty point sitting on the canvas.
Simulating ADC
From:
Flow,_,_ = self.Data.get_adc_val(1)
to
Flow = random.uniform(1, 5)
Obviously you could keep your own code for this
Looping with after
Your system of using the after function wasn't entirely correct, as you should have inherited the Flowmonitor.Flowdata from the pre-existing window. Otherwise you would be updating values that simply don't exist, hence, I replaced it with a self. function
root.after(25, Flowmonitor.Flowdata.update)
to
self.canvas.flush_events()
root.after(1,self.update)
I decreased the after value, to show that the window could continue plotting correctly when doing it even faster!
The flush_events() function causes the window to properly update and keep track of other things it's doing!
Answer to question 3
I'd thoroughly dissuade you from going down the threading route, because it is awful with tkinter. The amount of issues and loop-holes you have to jump through are awful, and quite often, even with threading, the program still begins to feel quite slow and unresponsive.
I am stuck. I have tried to code a figure into Tk from Matplotlib. I have done it before, but for some reason this is just not working.
I also don't understand when to use "()" to call a function and when not too:
#Example
def _Example(self)
print "Blah blah"
# Do I call?
self._Example # or
self._Example()
Sometimes one works and one doesn't?? AKA in the case of "events" in my code _UpdateCanvas??
That would be helpful, because now I can't seem to find out why my figure isn't loading?
# ----------------------- Import <<
from Tkinter import *
import matplotlib
import numpy as np
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
# ----------------------- Class <<
class App(Frame):
def __init__(self,master=None):
Frame.__init__(self,master)
self._job = None
self.canvas = None
self.Flag_Zoom = False
self.pack()
self._Data()
self._GUI_Design()
# Create Canvas + Load Canvas
self._Figure(self.Arm,self.Tri)
self.canvas = FigureCanvasTkAgg(self.f,self.LF_Graph)
self.canvas.draw()
self.canvas.show()
def _GUI_Design(self):
# FRAME
self.LF_Control = LabelFrame(self, text='Angle Controls', width=400, height=100 )
self.LF_Graph = LabelFrame(self, text='Figure', width=400, height=400)
self.LF_Control.grid(row=0, column=0, padx=5,pady=1)
self.LF_Graph.grid(row=1, column=0,padx=5,pady=3)
# LABELS
self.LB_Info = Label(self.LF_Control,text="MOTION CONTACT EXAMPLE\nwith spoiler controls\n2013-07-23", justify=LEFT)
self.LB_SP = Label(self.LF_Control, text="Spoiler:")
self.LB_FP = Label(self.LF_Control, text="Triangle:")
# SLIDERS
self.SpScale = Scale(self.LF_Control, from_=0, to=45, orient=HORIZONTAL, length=350, resolution=1, command=self._UpdateCanvas, tickinterval=5)
self.TrScale = Scale(self.LF_Control, from_=0, to=180, orient=HORIZONTAL, length=350, resolution=5, command=self._UpdateCanvas, tickinterval=30)
# BUTTONS
self.Bup = Button(self.LF_Control, text=" ^ \n | ", command=self._Bup)
self.Bdn = Button(self.LF_Control, text=" | \n V ", command=self._Bdn)
self.Blf = Button(self.LF_Control, text=" <- " , command=self._Blf)
self.Brt = Button(self.LF_Control, text=" -> " , command=self._Brt)
# GRIDS
self.LB_Info.grid(row=0, column=0,padx=1,pady=1,columnspan=2)
self.LB_SP.grid(row=1, column=0,padx=1,pady=1)
self.LB_FP.grid(row=2, column=0,padx=1,pady=1)
self.SpScale.grid(row=1, column=1,padx=1,pady=1)
self.TrScale.grid(row=2, column=1,padx=1,pady=1)
self.Bup.grid(row=0, column=4 ,padx=1,pady=1)
self.Bdn.grid(row=2, column=4 ,padx=1,pady=1)
self.Blf.grid(row=1, column=3 ,padx=1,pady=1)
self.Brt.grid(row=1, column=5 ,padx=1,pady=1)
print "Graphics Loaded"
def _Figure(self,Arm,Tri):
self.f = plt.figure(1)
plt.axis([-500,500,-500,500])
plt.axes().set_aspect('equal', 'datalim')
plt.plot(Arm[0],Arm[1],"r")
plt.plot(Tri[0],Tri[1],"g")
print "figure set",Arm,Tri
def _UpdateCanvas(self,event):
print "def:_UpdateCanvas"
self._job = self.after(100, self._Execute) #miliseconds
if self._job:
self.after_cancel(self._job)
def _Data(self):
self.Arm = [[0,100],[0,100]]
self.Tri = [[50,150,100,50],[-50,-40,-120,-50]]
def _Execute(self):
print "def:_Execute"
self.SpAngle = self.SpScale.get()
self.TrAngle = self.TrScale.get()
self._Figure(self.Arm,self.Tri)
self.canvas.draw()
self.canvas.show()
# Button Commands
def _Bup(self):
self.Tri[1] = [i+5 for i in self.Tri[1]]
print "Button: UP"
self._Execute()
def _Bdn(self):
self.Tri[1] = [i-5 for i in self.Tri[1]]
print "Button: DOWN"
self._Execute()
def _Blf(self):
self.Tri[0] = [i-5 for i in self.Tri[0]]
print "Button: LEFT"
self._Execute()
def _Brt(self):
self.Tri[0] = [i+5 for i in self.Tri[0]]
print "Button: RIGHT"
self._Execute()
# Execute App
root = Tk()
root.title("Contact")
Exe = App(master=root)
root.mainloop()
Please me understand why no figures are loading?
You always use parenthesis to call a function. You never use parenthesis if you are using the function as a parameter to a command attribute, or when using it in a bind statement.
The trick is knowing when you want to call a function, and when you want to use a reference to the function. With respect to Tkinter, the value of a command attribute, and the second argument to a bind statement must always be a reference to a function that will be called later.