real-time plotting to a TK window using python3 - python-3.x

I am trying to plot in real-time to a tkinter window in python3.
I am attempting to wrap my window in a class.
my code shows the graph, but data is not being plotted. Here is the code:
#! /usr/bin/python3
# -*- coding: utf-8 -*-
import sys
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
import tkinter
import time
import numpy as np
matplotlib.use("TkAgg")
class Window(tkinter.Frame):
def __init__(self, master = None):
tkinter.Frame.__init__(self, master)
self.master = master
self.master.grid()
self.fig = Figure(figsize=(5,5), dpi=100)
self.ax = self.fig.add_subplot(111)
self.t, self.y = [], []
self.init_window()
self.ani = animation.FuncAnimation(self.fig, self.animate, interval=1000)
def init_window(self):
# set window title
self.master.title("PlotSomething")
# run show_graph
self.show_graph()
def animate(self):
self.t.append(time.time())
self.y.append(np.random.random())
self.ax.clear()
self.ax.plot(self.t,self.y)
def show_graph(self):
canvas = FigureCanvasTkAgg(self.fig, self.master)
canvas.get_tk_widget().grid(row=1, column=1)
canvas.draw()
def client_exit(self):
sys.exit()
def main(args):
root = tkinter.Tk()
app = Window(root)
root.mainloop()
return 0
if __name__=="__main__":
sys.exit(main(sys.argv[1:]))
Please let me know what the issue is.
Bonus if you can help me get the real-time plot stuff into a separate class that I can call with my tkinter window.
I obviously need some learning on how to create and call classes using tkinter.
As always thank you for your support!

Related

Tkinter Window too small

My kinter window is coming to small even after using geometry.
What to do so that it doesn't shrink?
Here's my code. And I can see the minimize button is coming towards left of the window. but I don't know ... why does this happen? I want to achieve oop for tkinter.
from logging import root
from tkinter import *
import tkinter as tk
from tkinter import messagebox
import mysql.connector
from tkinter import ttk
# from com.acc.report.database import
class Main(tk.Tk):
def __init__(self):
super().__init__()
# if not tasks:
# self.tasks = []
# instance of tkinter frame, i.e., Tk()
# root = Tk()
style = ttk.Style()
style.map("C.TButton",
foreground=[('pressed', 'red'), ('active', 'blue')],
background=[('pressed', '!disabled', 'black'), ('active', 'white')]
)
# width = self.winfo_screenwidth()
# height = self.winfo_screenheight()
self.geometry("626x431")
def selectReports():
messagebox.showinfo("EDP", "All reports")
def showReports():
messagebox.showinfo("EDP", "Select reports")
#Report Display Window
# def reviewReport():
# win = Toplevel(root)
# win.geometry("626x431")
monitor = ttk.Button(name="",text="Monitor",command=showReports,style="C.TButton")
review = ttk.Button(name="",text="Review",command=selectReports,style="C.TButton")
monitor.pack(pady=100)
review.pack(pady=0)
# Main method
if __name__ == "__main__":
objectMain = Main()
objectMain.withdraw()
objectMain.mainloop()
Make showReports and selectReports class methods, comment out or remove objectMain.withdraw() and everything should work.
from logging import root
from tkinter import *
import tkinter as tk
from tkinter import messagebox
#import mysql.connector
from tkinter import ttk
# from com.acc.report.database import
class Main(tk.Tk):
def __init__(self):
super().__init__()
self.geometry("626x431")
style = ttk.Style()
style.map("C.TButton",
foreground=[('pressed', 'red'), ('active', 'blue')],
background=[('pressed', '!disabled', 'black'), ('active', 'white')]
)
monitor = ttk.Button(name="",text="Monitor",command=self.showReports,style="C.TButton")
review = ttk.Button(name="",text="Review",command=self.selectReports,style="C.TButton")
monitor.pack(pady=100)
review.pack(pady=0)
def selectReports(self):
messagebox.showinfo("EDP", "All reports")
def showReports(self):
messagebox.showinfo("EDP", "Select reports")
# Main method
if __name__ == "__main__":
objectMain = Main()
# objectMain.withdraw()
objectMain.mainloop()

Displaying plot in Tkinter GUI frame

I want to show the plot canvas in the top right side of UI (using Tkinter python3). I wrote the following code but I got the following error:
canvas = FigureCanvasTkAgg(fig, master=root) NameError: name 'root' is not defined
My code is:
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import sys
class GUI(tk.Frame):
def __init__(self, master = None):
self.root = tk.Tk()
self.root.geometry("500x500")
tk.Frame.__init__(self, master)
self.createWidgets()
def start(self):
self.root.mainloop()
def createWidgets(self):
fig = plt.figure(figsize=(8, 8))
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().grid(row=0, column=1)
canvas.show()
def main():
appstart = GUI()
appstart.start()
if __name__ == "__main__":
main()
I made some changes to your code, deleting some unnecessary
references such as:
import tkinter.ttk as ttk
from tkinter import *
from matplotlib.figure import Figure
import sys
self.root = tk.Tk()
tk.Frame.__init__(self, master)
that you dont' use and I think only confuse yourself.
I've even simplified your script and add this code below to show to you wath is "self"
print(type(self))
for item in dir(self):
print(type(item),item)
I've add even a toolbar to the plot and some data to plot something.
Regards
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import numpy as np
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.title("Hello World")
self.geometry("500x500")
self.createWidgets()
print(type(self))
for item in dir(self):
print(type(item),item)
def createWidgets(self):
t = np.arange(0, 3, .01)
f0 = tk.Frame()
fig = plt.figure(figsize=(8, 8))
fig.add_subplot(111).plot(t, 2 * np.sin(2 * np.pi * t))
canvas = FigureCanvasTkAgg(fig, f0)
toolbar = NavigationToolbar2Tk(canvas, f0)
toolbar.update()
canvas._tkcanvas.pack(fill=tk.BOTH, expand=1)
f0.pack(fill=tk.BOTH, expand=1)
def main():
appstart = GUI()
appstart.mainloop()
if __name__ == "__main__":
main()
In your code you should use self.root if your intent is to use the root window:
canvas = FigureCanvasTkAgg(fig, master=self.root)
... or maybe just self, if your intent is to have it appear inside the frame. I'm not entirely sure what you are intending to do
canvas = FigureCanvasTkAgg(fig, master=self)
Somewhat unrelated, if you're explicitly creating a root window, you should be passing that to tk.Frame.__init__ rather than master.
tk.Frame.__init__(self, self.root)

Closing a pyplot object in Tkinter while working with PdfPages causes the Tkinter instance to stop

I apologize for the title but I couldn't think of a better description of the problem. I have a Tkinter based program that has an option for the user to generate a PDF report that consists of an overview, followed by some detail plots. I knew that for some reason the entire program would shut down after it finalized the PDF report but I only recently sat down to truly identify what caused it.
I found that the plt.close line in the initial overview plot, causes the entire program to close once the pdf report has been written (which is the first part that I don't understand as surely, if the plot.close is to blame why does the entire module run til completion)? Secondly, why does this even happen?
The minimum example that I was able to produce (with nonsense data for the plots) is listed below where if the line that is preceded by # THE CULPRIT is commented, the Tk() instance stays alive but if it's left as is, the Tk() instance is closed.
import tkinter as tk
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_pdf import PdfPages
from pathlib import Path
class Pdf(object):
def __init__(self, master):
self.master = master
pdf = PdfPages(Path.cwd() / 'demo.pdf')
self.pdf = pdf
def plot_initial(self):
fig = plt.figure(figsize=(8,6))
fig.add_subplot(111)
mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)
count, bins, ignored = plt.hist(s, 30, density=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
linewidth=2, color='r')
plt.title('Overview')
plt.xlabel('X')
plt.ylabel('Y')
self.pdf.savefig(fig)
# THE CULPRIT
plt.close(fig)
def plot_extra(self):
fig = plt.figure(figsize=(8,6))
fig.add_subplot(111)
mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)
count, bins, ignored = plt.hist(s, 30, density=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
linewidth=2, color='r')
plt.title('Extra')
plt.xlabel('X')
plt.ylabel('Y')
self.pdf.savefig(fig)
plt.close(fig)
def close(self):
self.pdf.close()
class MVE(object):
#classmethod
def run(cls):
root = tk.Tk()
MVE(root)
root.mainloop()
def __init__(self, master):
self.root = master
tk.Frame(master)
menu = tk.Menu(master)
master.config(menu=menu)
test_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label='Bug', menu=test_menu)
test_menu.add_command(label='PDF', command=
self.generate_pdf)
def generate_pdf(self):
pdf = Pdf(self)
pdf.plot_initial()
for i in range(0,3):
pdf.plot_extra()
pdf.close()
if __name__ == "__main__":
MVE.run()
Versions of installed packages/python base:
Python 3.7.0
Tkinter 8.6
Matplotlib 2.2.3
Numpy 1.15.1
Edit
I have upgraded to Matplotlib 3.0.2 as per #ImportanceOfBeingErnest suggestion, however the problem still remains.
Seems like that the default backend used is TkAgg, change it to non-interactive backend, like agg, before importing matplotlib.pyplot:
import tkinter as tk
import matplotlib as mpl
mpl.use('agg')
import matplotlib.pyplot as plt
...
The problem arises because: "tension in Matplotlib between providing a low-level library to be used by application developers and being a front-line user interface. In this case, the niceties we need to provide for the end-user (ex, exiting the GUI main loop when the last figure is closed) is conflicting with how #Tarskin wants to use Matplot as a low-level library.". For the full comment see here.
The problem is apparantly caused by matplotlib/lib/matplotlib/backends/_backend_tk.py:
def destroy(self, *args):
if self.window is not None:
#self.toolbar.destroy()
if self.canvas._idle_callback:
self.canvas._tkcanvas.after_cancel(self.canvas._idle_callback)
self.window.destroy()
if Gcf.get_num_fig_managers() == 0:
if self.window is not None:
self.window.quit()
self.window = None
which quits the application when all of the figures are closed. This is essential for plt.show(block=True) to work (so when you close all of the plots we return control to the terminal). For the source of this go here.
The suggestion was made to use Agg and while this indeed fixes it for now, the suggestion was made to ignore pyplot completely, when one is integrating matplotlib into a stand-alone package. Therefore, I have now fixed this by using just the matplotlib.figure.Figure class, as listed below (with a bit more nonsensical data, to avoid the use of pyplot completely).
import tkinter as tk
from matplotlib.figure import Figure
import numpy as np
from matplotlib.backends.backend_pdf import PdfPages
from pathlib import Path
class Pdf(object):
def __init__(self, master):
self.master = master
pdf = PdfPages(Path.cwd() / 'demo.pdf')
fig = Figure(figsize=(8,6))
axes = fig.add_subplot(111)
axes.set_xlabel('X')
axes.set_ylabel('Y')
self.fig = fig
self.axes = axes
self.pdf = pdf
def plot_initial(self):
mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)
self.axes.clear()
self.axes.plot(s)
self.axes.set_title('Overview')
self.pdf.savefig(self.fig)
def plot_extra(self):
mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)
self.axes.clear()
self.axes.plot(s)
self.axes.set_title('Extra')
self.pdf.savefig(self.fig)
def close(self):
self.pdf.close()
class MVE(object):
#classmethod
def run(cls):
root = tk.Tk()
MVE(root)
root.mainloop()
def __init__(self, master):
self.root = master
tk.Frame(master)
menu = tk.Menu(master)
master.config(menu=menu)
test_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label='Fixed', menu=test_menu)
test_menu.add_command(label='PDF', command=
self.generate_pdf)
def generate_pdf(self):
pdf = Pdf(self)
pdf.plot_initial()
for i in range(0,3):
pdf.plot_extra()
pdf.close()
if __name__ == "__main__":
MVE.run()
The full discussion can be found at this github issue thread.

How to display multicursor on a QTabWidget?

The multicursor example
The question is : If I want the plot to be displayed on a tab of the QTabWidget,how to make the MultiCursor works?
# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
import numpy as np
import sys
from matplotlib.gridspec import GridSpec
from matplotlib.widgets import MultiCursor
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
class MainWindow(QMainWindow):
def __init__(self):
super().__init__(flags=Qt.Window)
self.setFont(QFont("Microsoft YaHei", 10, QFont.Normal))
self.setMinimumSize(1550, 950)
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
centralwidget = QWidget(flags=Qt.Widget)
centralwidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setCentralWidget(centralwidget)
self.tabview = QTabWidget()
self.tabview.currentChanged.connect(self.onchange)
self.chart_widget0 = QWidget()
self.chart_widget1 = QWidget()
self.dc0 = my_Canvas(self.chart_widget0, width=20, height=8, dpi=100)
self.dc1 = my_Canvas(self.chart_widget1, width=20, height=8, dpi=100)
self.tabview.addTab(self.dc0, "MultiCursor")
self.tabview.addTab(self.dc1, "Cursor")
toplayout = QHBoxLayout()
toplayout.addWidget(self.tabview)
centralwidget.setLayout(toplayout)
def onchange(self,i):
if i == 0:
self.dc0.update_figure()
elif i == 1:
self.dc1.update_figure()
class my_Canvas(FigureCanvas):
def __init__(self, parent=None, width=10, height=7, dpi=100):
self.fig = plt.figure(figsize=(width, height), dpi=dpi)
gs = GridSpec(2, 1, height_ratios=[3, 1])
self.axes1 = plt.subplot(gs[0])
self.axes2 = plt.subplot(gs[1])
self.compute_initial_figure()
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
def compute_initial_figure(self):
self.axes1.cla()
self.axes2.cla()
def update_figure(self):
t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)
s2 = np.sin(4*np.pi*t)
self.axes1.plot(t, s1)
self.axes2.plot(t, s2)
multi = MultiCursor(self.fig.canvas, (self.axes1, self.axes2), color='r', lw=1)
self.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
w1 = MainWindow()
w1.show()
sys.exit(app.exec_())
How to modify the code to make the MultiCursor works, and could I control the display of the cursor by key or mousebutton click?
Further more, how to display the coordinate with the cursor?
As the Multicursor documentation tells us,
For the cursor to remain responsive you must keep a reference to it.
The easiest way is to make it a class variable,
self.multi = MultiCursor(...)

Reading frames from a folder and display in QGraphicsView in pyqt4

I am trying to read frame for a folder and display in QGraphics view.
import os
import PyQt4
from PyQt4 import QtCore, QtGui
dir = "frames"
for file in os.listdir(dir):
grview = QtGui.QGraphicsView()
scene = QtGui.QGraphicsScene()
#pixmap = QtGui.QPixmap(os.path.join(dir, file))
pixmap = QtGui.QPixmap(file)
item = QtGui.QGraphicsPixmapItem(pixmap)
self.scene.addItem(item)
grview.setScene()
grview.show()
#self.scene.update()
The folder named "frames" contains jpg files and is in the same folder as the code. But still I am not able to read the frames.
I have also tried general display without graphicsview to check if it works using the code below but it too doesn't work.
import cv2
import os
import PyQt4
from PyQt4 import QtGui,QtCore
import matplotlib.pyplot as plt
from PIL import Image
def load_images_from_folder(folder):
images = []
for filename in os.listdir(folder):
img = cv2.imread(os.path.join(folder,filename))
if img is not None:
images.append(img)
return images
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
img = load_images_from_folder('/frames')
image = Image.open(img)
image.show()
I am not able to understand what am I missing. I want to display the images sequentially in QGraphicsView resembling a video and in some particular frame I have to select the object of interest by draw a rectangle around it which has to hold in the subsequent frames as well. Is the task i am attempting possible? if yes any help is appreciated
Sorry, but I have PyQt5.
Try it:
import os
import sys
from PyQt5 import Qt
#from PyQt4 import QtCore, QtGui
class MainWindow(Qt.QMainWindow):
def __init__(self, parent=None):
Qt.QMainWindow.__init__(self, parent)
self.scene = Qt.QGraphicsScene()
self.grview = Qt.QGraphicsView(self.scene)
self.setCentralWidget(self.grview)
dir = "frames"
self.listFiles = os.listdir(dir)
self.timer = Qt.QTimer(self)
self.n = 0
self.timer.timeout.connect(self.on_timeout)
self.timer.start(1000)
self.setGeometry(700, 150, 300, 300)
self.show()
def on_timeout(self):
if self.n < len(self.listFiles):
self.scene.clear()
file = self.listFiles[self.n]
pixmap = Qt.QPixmap("frames\{}".format(file)) # !!! "frames\{}"
item = Qt.QGraphicsPixmapItem(pixmap)
self.scene.addItem(item)
self.grview.setScene(self.scene) # !!! (self.scene)
self.n += 1
else:
self.timer.stop()
app = Qt.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())

Resources