I am writing a program that does some work and uses matplotlib to plot some data. This can take some time so I set up a progressbar using tkinter. Threading with tkinter was not that easy. I am running the progressbar in the main thread and my working stuff in a substhread. However I can not close the progressbar window after my work is done, because apparently matplotlib does something in the tk root window. I don't know what. I added a minimal example of what I am trying to do. Notice that removing the line "plotsomething()" makes it do what I want: close the progressbar after work is done.
Can you please help me figuring out how I can close the progressbar window without closing the matplotlib windows?
# coding = utf-8
import numpy as np
import matplotlib.pyplot as plt
import tkinter as tk
from tkinter import ttk
import threading, queue
import time
def MAIN():
PB = q.get()
for i in np.arange(10):
time.sleep(0.2)
print(i)
PB.step(10)
PB.update()
print("Done")
def plotsomething():
x = np.linspace(0,10,100)
y = np.sin(x)
plt.plot(x,y)
root = tk.Tk()
root.title("Progress")
PB = ttk.Progressbar(root, orient = "horizontal",length=300, mode = 'determinate')
PB.pack()
q = queue.Queue()
q.put(PB)
plotsomething()
T = threading.Thread(target=MAIN(), name="MAIN")
T.start()
T.join()
plt.show()
EDIT - SOLUTION: I am solving the problem now by drawing every window seperatly by using the matplotlib tk backend. Apparently PyPlot is interfering with the tkinter root windows. See tcaswell's comment for more details and hints. Thank you very much!
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import tkinter as tk
from tkinter import ttk
import queue, threading, time
def center_window(window_parent, w=300, h=20):
# get screen width and height
ws = window_parent.winfo_screenwidth()
hs = window_parent.winfo_screenheight()
# calculate position x, y
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
window_parent.geometry('%dx%d+%d+%d' % (w, h, x, y))
def MAIN():
PB = q.get()
for i in np.arange(10):
time.sleep(0.2)
print(i)
PB.step(10)
PB.update()
print("Done")
root = tk.Tk()
root.wm_title("Embedding in TK")
f = Figure(figsize=(5,4), dpi=100)
a = f.add_subplot(111)
t = arange(0.0,3.0,0.01)
s = sin(2*pi*t)
a.plot(t,s)
a.set_title('Tk embedding')
a.set_xlabel('X axis label')
a.set_ylabel('Y label')
#a tk.DrawingArea
root2 = tk.Tk()
PB = ttk.Progressbar(root2, orient = "horizontal",length=300, mode = 'determinate')
PB.pack()
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
toolbar = NavigationToolbar2TkAgg( canvas, root )
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
root2.iconify()
root2.update()
root2.deiconify()
center_window(root2)
q = queue.Queue()
q.put(PB)
T = threading.Thread(target=MAIN(), name="MAIN")
T.start()
T.join()
root2.quit()
root2.destroy()
tk.mainloop()
You are getting conflicting gui-main loops between the TK you are starting and the TK that plt is start. If you want to use matplotlib with your own gui, you must embed it your self and you can not import pyplot. All the behind the scenes magic that makes the pyplot interface wonderful is what is messing you up here.
For a tutorial see here, for how mpl does the embedding see here.
Also see:
Unable to save matplotlib.figure Figure, canvas is None
Python Matplotlib runtime error upon closing the console
Matplotlib bar chart in a wx Frame instead of a new window
Related
I would like to pick lines that have been drawn on an image by using cv2.line and do something with them. To realise that I had a look on matplotlibs picker and even found a good example here. As I am going to use tkinter for GUI I added it to my MWE.
Code from example which works fine:
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import numpy as np
root = Tk.Tk()
root.iconify()
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots()
for i in range(1, 10):
ax.plot(x, i * x + x, picker=5)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def on_pick(event):
event.artist.set_visible(not event.artist.get_visible())
fig.canvas.draw()
fig.canvas.callbacks.connect('pick_event', on_pick)
root.mainloop()
Now my code using OpenCV which does not work:
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import cv2
root = Tk.Tk()
root.iconify()
img = np.zeros([100,100,3],dtype=np.uint8)
img.fill(255)
cv2.line(img,(10,10),(60,90),(100,149,237),2)
fig = Figure()
ax = fig.add_subplot(111)
ax.set_aspect('equal')
ax.plot()
ax.imshow(img)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def onpick1(event):
event.artist.set_visible(not event.artist.get_visible())
fig.canvas.draw()
fig.canvas.callbacks.connect('pick_event', onpick1)
root.mainloop()
In both examples onpick1 should set the lines visibility on or off by a single mouse click but it doesn't. My assumption is that it has something to do with the way it is plotted or how I draw the lines (ax.plt vs cv2.line). I would be very happy about any help. Thanks!
I am trying to show up the line graphs (Strings and Numbers )as shown in the array) in a Canvas. I got this code from different questions in this forums, trying to modify for my requirements.Can some one please guide me.
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({"Week": ['Week1','Week2','Week3','Week4','Week5'],
"App1" : [2.6,3.4,3.25,2.8,1.75],
"App2" : [2.5,2.9,3.0,3.3,3.4],
"App3" : [1.6,2.4,1.25,5.8,6.75]})
df.plot(x="Week", y=["App1", "App2", "App3"])
plt.show()
Here is the one, in case if someone else is looking.
# --- matplotlib ---
import matplotlib
matplotlib.use('TkAgg') # choose backend
from tkinter import messagebox
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,
NavigationToolbar2TkAgg
from matplotlib.pyplot import Figure
from matplotlib import pyplot as plt
# --- other ---
import tkinter as tk
import pandas as pd
# --- GUI ---
root = tk.Tk()
# top frame for canvas and toolbar - which need `pack()` layout manager
top = tk.Frame(root)
top.pack()
# bottom frame for other widgets - which may use other layout manager
bottom = tk.Frame(root)
bottom.pack()
# create figure
fig = matplotlib.pyplot.Figure()
# create matplotlib canvas using `fig` and assign to widget `top`
canvas = FigureCanvasTkAgg(fig, top)
# get canvas as tkinter widget and put in widget `top`
canvas.get_tk_widget().pack()
canvas._tkcanvas.pack()
# --- plot ---
data = {"Week": ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],
"App1" : [2.6,3.4,3.25,2.8,1.75,5,2],
"App2" : [2.5,2.9,3.0,3.3,3.4,5,3],
"App3" : [1.6,15,1.25,5.8,6.75,6,4]
}
new_df = pd.DataFrame(data)
ax = fig.add_subplot(111)
fig.suptitle('Graph Title', fontsize=12)
new_df.plot(x="Week", y=["App1", "App2", "App3"],ax=ax)
def on_closing():
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.lift()
root.attributes('-topmost',True)
root.mainloop()
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()
I have a code where I am embedding a matplotlib graphic in TkInter. This works fine and the data updates, but when I try to use commands like plt.x_ticks(rotation="vertical") it doesn't do it, and any other command that starts with plt. doesn't work. Normally in MatPlotLib w/o embedding, this adjust features of the graph. Why isn't it working/ why and how is embedding changing my code?
A simpler example code where I have the same problem is shown here. This one just plots the same sin function over and over, but it doesn't rotate the axis ticks, or apply the proper axis range.
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
import numpy as np
from pylab import *
from datetime import datetime
from time import localtime, strftime
import time
import serial
import re
import sys
import exceptions
if sys.version_info[0]<3:
import Tkinter as Tk
else:
import tkinter as Tk
#############################################
#############################################
# All of the following is Specific to Tk #
# and getting the window with buttons open #
#############################################
#############################################
def mainWindow():
global i
global fig
global canvas
root.wm_title("Practice with TK")
fig=Figure(figsize=(7,5),dpi=100)
canvas=FigureCanvasTkAgg(fig,master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP,fill=Tk.BOTH,expand=1)
toolbar=NavigationToolbar2TkAgg(canvas,root)
toolbar.update()
button=Tk.Button(master=root, text="Plot", command=startPlotting)
button.pack(side=Tk.RIGHT)
button=Tk.Button(master=root, text="StopPlot", command=stopPlotting)
button.pack(side=Tk.RIGHT)
button=Tk.Button(master=root, text="QUIT", command=quit)
button.pack(side=Tk.LEFT)
Tk.mainloop()
def plotting():
global i
global j
l=i
if l==1:
j+=1
global fig
ax1=fig.add_subplot(111)
t=arange(0.0,3.0,.01)
s=np.sin(2*np.pi*t)
ax1.plot(t,s)
print(str(j)+":You have plotted")
plt.axis([0,4,-1.5,1.5])
plt.xticks(rotation='vertical')
canvas.show()
root.after(2000,plotting)
else:
pass
def startPlotting():
global i
global j
j=0
i=1
print i
plotting()
def stopPlotting():
global i
i=0
print i
def quit():
root.quit()
root.destroy()
global i
i=0
global root
root=Tk.Tk()
mainWindow()
For a demonstration of a Graph algorithm i need to draw a networkx graph to a Tkinter Canvas and be able to modify that graph (and the plot) at runtime.
I have pieced together the following code (I hope it is the minimal code leading to my problem, but I'm new to this so I'm not sure):
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import Tkinter as Tk
import networkx as nx
from tkMessageBox import showinfo
root = Tk.Tk()
root.wm_title("Animated Graph embedded in TK")
root.wm_protocol('WM_DELETE_WINDOW', root.quit())
f = plt.figure(figsize=(5,4))
a = f.add_subplot(111)
plt.axis('off')
# the networkx part
G=nx.complete_graph(5)
nx.draw_networkx(G,pos=nx.spring_layout(G),ax=a)
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def next_graph():
if G.order():
a.cla()
G.remove_node(G.nodes()[-1])
nx.draw(G, pos=nx.circular_layout(G), ax=a)
canvas.draw()
b = Tk.Button(root, text="next",command=next_graph)
b.pack()
Tk.mainloop()
My problem now is this:
The first display of the graph is like I want it (backgroundcolor-wise), but after you first click 'Next' the backgroundcolor of the graph changes to white. I have tried changing the background color of the figure and the canvas.
I don't even know what brings that change about, I think it is simply drawing to the same canvas twice.
How can I modify the code to have the graph always have the same background color?
On an unrelated note: the root.quit() I added does not help in ending the application properly. This might be stupid on my side, but what did go wrong here?
I think you are very close. If you use nx.draw_networkx() in your event loop then it works (turn off the axis there too).
Here is your example with those modifications and also with a single layout computed at the beginning that is reused in the loop:
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import Tkinter as Tk
import networkx as nx
from tkMessageBox import showinfo
root = Tk.Tk()
root.wm_title("Animated Graph embedded in TK")
# Quit when the window is done
root.wm_protocol('WM_DELETE_WINDOW', root.quit)
f = plt.figure(figsize=(5,4))
a = f.add_subplot(111)
plt.axis('off')
# the networkx part
G=nx.complete_graph(5)
pos=nx.circular_layout(G)
nx.draw_networkx(G,pos=pos,ax=a)
xlim=a.get_xlim()
ylim=a.get_ylim()
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def next_graph():
if G.order():
a.cla()
G.remove_node(G.nodes()[-1])
nx.draw_networkx(G, pos, ax=a)
a.set_xlim(xlim)
a.set_ylim(ylim)
plt.axis('off')
canvas.draw()
b = Tk.Button(root, text="next",command=next_graph)
b.pack()
Tk.mainloop()