How to add a live (updating) graph to my PyQt5 Window using Serial? - matplotlib

I am writting a program that's supposed to receive 4 random values from my Arduino every second and create an updating bar chart with them. I then want to create a PyQt5 Window to show that bar chart. I was sucessful at reading the values coming from my Arduino and using them to create the bar chart. However, since I'm a beginner when it comes to GUI/Arduino, I have no clue how to add that bar chart to my PyQt Window. I'd appreciate if you could help me with that.
Here's my Arduino code:
void setup() {
Serial.begin(115200);
}
void loop() {
float nb1 = random(0, 100);
float nb2 = random(0, 100);
float nb3 = random(0, 100);
float nb4 = random(0, 100);
Serial.print(nb1);
Serial.print(" , ");
Serial.print(nb2);
Serial.print(" , ");
Serial.print(nb3);
Serial.print(" , ");
Serial.println(nb4);
delay(1000);
}
and here's what the graph should look like (the titles and labels are in french, but don't mind that):
import serial
import numpy
import matplotlib.pyplot as plt
from drawnow import *
plt.ion()
def make_figure():
plt.ylim(0, 100)
plt.title("Titre que tu veux")
plt.tick_params(axis = 'x', which = 'both', bottom = False, top = False, labelbottom = False)
plt.hlines(16, -0.5, 0.5, color = 'g')
plt.ylabel("Nom de la variable")
plt.bar(0, nb1, color = 'b')
plt.bar(2, nb2, color = 'b')
plt.hlines(42, 1.5, 2.5, color = 'g')
plt.bar(4, nb3, color = 'b')
plt.hlines(32, 3.5, 4.5, color = 'g')
plt.bar(6, nb4, color = 'b')
plt.hlines(80, 5.5, 6.5, color = 'g')
with serial.Serial('COM3', baudrate = 115200) as ser:
while True:
while (ser.inWaiting() == 0):
pass
string_received = ser.readline().decode().strip('\r\n')
dataArray = string_received.split(" , ")
nb1 = float(dataArray[0])
nb2 = float(dataArray[1])
nb3 = float(dataArray[2])
nb4 = float(dataArray[3])
drawnow(make_figure)
plt.pause(0.000001)
Now, here is what my Window should look like, but replace the static chart with a dynamic one (don't mind the buttons, they're not meant to do anything right now):
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("Titre que tu veux")
MainWindow.resize(1510, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushgetMax = QtWidgets.QPushButton(self.centralwidget)
self.pushgetMax.setGeometry(QtCore.QRect(1240, 30, 93, 28))
self.pushgetMax.setObjectName("pushgetMax")
self.pushAll = QtWidgets.QPushButton(self.centralwidget)
self.pushAll.setGeometry(QtCore.QRect(1350, 30, 93, 28))
self.pushAll.setObjectName("pushAll")
self.pushButton1 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton1.setGeometry(QtCore.QRect(1240, 70, 93, 28))
self.pushButton1.setObjectName("pushButton1")
self.pushButton2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton2.setGeometry(QtCore.QRect(1350, 70, 93, 28))
self.pushButton2.setObjectName("pushButton2")
self.pushButton3 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton3.setGeometry(QtCore.QRect(1240, 110, 93, 28))
self.pushButton3.setObjectName("pushButton3")
self.pushButton_6 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_6.setGeometry(QtCore.QRect(1350, 110, 93, 28))
self.pushButton_6.setObjectName("pushButton_6")
self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(20, 10, 1191, 531))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
barre1 = MplCanvas(self, width=5, height=4, dpi=100)
barre1.axes.set_ylim([0, 100])
barre1.axes.set_title('Titre 1')
barre1.axes.tick_params(axis = 'x', which = 'both', bottom = False, top = False, labelbottom = False)
barre1.axes.bar(0, 22, color = 'b')
barre1.axes.hlines(25, -0.5, 0.5, color = 'g')
barre1.axes.bar(2, 50, color = 'b')
barre1.axes.hlines(60, 1.5, 2.5, color = 'g')
barre1.axes.bar(4, 32, color = 'b')
barre1.axes.hlines(50, 3.5, 4.5, color = 'g')
barre1.axes.bar(6, 81, color = 'b')
barre1.axes.hlines(70, 5.5, 6.5, color = 'g')
self.horizontalLayout.addWidget(barre1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1510, 26))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushgetMax.setText(_translate("MainWindow", "GetMax"))
self.pushAll.setText(_translate("MainWindow", "All"))
self.pushButton1.setText(_translate("MainWindow", "1"))
self.pushButton2.setText(_translate("MainWindow", "2"))
self.pushButton3.setText(_translate("MainWindow", "3"))
self.pushButton_6.setText(_translate("MainWindow", "4"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())

If you are going to use PyQt5 then forget about pyplot since you have to use the respective canvas. On the other hand do not use pyserial since it is better to use QSerialPort since it uses the Qt eventloop.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets, QtSerialPort
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
class SerialPortManager(QtCore.QObject):
dataChanged = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
super().__init__(parent)
self._serial = QtSerialPort.QSerialPort(baudRate=115200)
self.serial.setPortName("COM3")
self.serial.readyRead.connect(self.on_ready_read)
#property
def serial(self):
return self._serial
def start(self):
self.serial.open(QtCore.QIODevice.ReadOnly)
#QtCore.pyqtSlot()
def on_ready_read(self):
if self.serial.canReadLine():
line = self.serial.readLine().data().decode()
values = line.strip().split(",")
try:
data = list(map(float, values))
except ValueError as e:
print("error", e)
else:
self.dataChanged.emit(data)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
fig = Figure(figsize=(5, 4), dpi=100)
self.canvas = FigureCanvas(fig)
self.max_button = QtWidgets.QPushButton(self.tr("GetMax"))
self.all_button = QtWidgets.QPushButton(self.tr("All"))
self.one_button = QtWidgets.QPushButton(self.tr("1"))
self.two_button = QtWidgets.QPushButton(self.tr("2"))
self.three_button = QtWidgets.QPushButton(self.tr("3"))
self.four_button = QtWidgets.QPushButton(self.tr("4"))
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
hlay = QtWidgets.QHBoxLayout(central_widget)
hlay.addWidget(self.canvas, stretch=1)
grid_layout = QtWidgets.QGridLayout()
grid_layout.addWidget(self.max_button, 0, 0)
grid_layout.addWidget(self.all_button, 0, 1)
grid_layout.addWidget(self.one_button, 1, 0)
grid_layout.addWidget(self.two_button, 1, 1)
grid_layout.addWidget(self.three_button, 2, 0)
grid_layout.addWidget(self.four_button, 2, 1)
vlay = QtWidgets.QVBoxLayout()
vlay.addLayout(grid_layout)
vlay.addStretch()
hlay.addLayout(vlay)
self.axes = self.canvas.figure.add_subplot(111)
self.axes.set_ylim([0, 100])
self.axes.set_title("Titre 1")
self.axes.tick_params(
axis="x", which="both", bottom=False, top=False, labelbottom=False
)
self.axes.hlines(25, -0.5, 0.5, color="g")
self.axes.hlines(60, 1.5, 2.5, color="g")
self.axes.hlines(50, 3.5, 4.5, color="g")
self.axes.hlines(70, 5.5, 6.5, color="g")
self.containers = []
self.update_bars([0, 0, 0, 0])
self.resize(640, 480)
#QtCore.pyqtSlot(list)
def update_bars(self, values):
if len(values) == 4:
[c.remove() for c in self.containers]
self.containers = []
for index, value in zip((0, 2, 4, 6), values):
c = self.axes.bar(index, value, color="b")
self.containers.append(c)
self.canvas.draw()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
manager = SerialPortManager()
manager.dataChanged.connect(w.update_bars)
manager.start()
sys.exit(app.exec_())

Related

why matplotlib Button not work with pyqt5?

I now want to use the Custom Button function of matplotlib, refer to this document: https://matplotlib.org/stable/gallery/widgets/buttons.html, it works quite well.
But when I want to combine it with PyQt5, I first create a main window with a button in it. When the button is clicked, a plot will pop up, but the button in the plot loses its response.
code show as below:
import sys
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Button
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
class Index:
ind = 0
def __init__(self, l, freqs):
self.l = l
self.freqs = freqs
def next(self, event):
self.ind += 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * t)
self.l.set_ydata(ydata)
plt.draw()
def prev(self, event):
self.ind -= 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * t)
self.l.set_ydata(ydata)
plt.draw()
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'PyQt5 button - pythonspot.com'
self.left = 10
self.top = 10
self.width = 320
self.height = 200
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
button = QPushButton('PyQt5 button', self)
button.setToolTip('This is an example button')
button.move(100, 70)
button.clicked.connect(self.on_click)
self.show()
#pyqtSlot()
def on_click(self):
freqs = np.arange(2, 20, 3)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
t = np.arange(0.0, 1.0, 0.001)
s = np.sin(2 * np.pi * freqs[0] * t)
l, = ax.plot(t, s, lw=2)
callback = Index(l, freqs)
axprev = fig.add_axes([0.7, 0.05, 0.1, 0.075])
axnext = fig.add_axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(callback.prev)
plt.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I want to know why?
In this question:
matplotlib event doesn't work when I use button clicked connect in pyqt5, I see that it seems to define a window myself , and then embed matplotlib, but I don't understand why
Is there a document that says we must do this?
I tried Macos, linux, windows, it works under macos, but the button doesn't respond under linux and windows.
I suspect it has something to do with QCoreApplication::exec: The event loop is already running, but I don't understand why the qt problem affects matplotlib. Is the signal of matplotlib registered to pyqt5?
Yes, Yes, You need not to a PlotEx, ref to Why aren't the matplotlib checkboxes working in pyQt5?
I understood that is because the button is local var, I need a more big scope.
the right code is :
import sys
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Button
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
class Index:
ind = 0
def __init__(self, l, freqs, t):
self.l = l
self.freqs = freqs
self.t = t
def next(self, event):
self.ind += 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * self.t)
self.l.set_ydata(ydata)
plt.draw()
def prev(self, event):
self.ind -= 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * self.t)
self.l.set_ydata(ydata)
plt.draw()
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'PyQt5 button - pythonspot.com'
self.left = 10
self.top = 10
self.width = 320
self.height = 200
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
button = QPushButton('PyQt5 button', self)
button.setToolTip('This is an example button')
button.move(100, 70)
button.clicked.connect(self.on_click)
self.show()
#pyqtSlot()
def on_click(self):
freqs = np.arange(2, 20, 3)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
t = np.arange(0.0, 1.0, 0.001)
s = np.sin(2 * np.pi * freqs[0] * t)
l, = ax.plot(t, s, lw=2)
callback = Index(l, freqs, t)
axprev = fig.add_axes([0.7, 0.05, 0.1, 0.075])
axnext = fig.add_axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(callback.prev)
plt.bnext = bnext
plt.bprev = bprev
plt.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
The difference is add (and fix some bugs):
plt.bnext = bnext
plt.bprev = bprev

Pass other functions returned value to QtableView in PYQt5

I am stuck on the last bit of the code to create a small search engine. So far I have been able to let users do some actions as select a folder where the files to search are stored, create an index, search for keyword and then export excerpt of the text around the keyword to a txt file. This is the layout
And this is the code I have used:
from PyQt5 import QtCore, QtGui, QtWidgets, QtWidgets
from PyQt5.QtWidgets import QHeaderView
import os, os.path
import glob
import os
from PyPDF2 import PdfFileReader, PdfFileWriter
import pdftotext
from whoosh import index
from whoosh.fields import Schema, TEXT, ID, STORED
from whoosh.analysis import RegexTokenizer
from whoosh.analysis import StopFilter
from whoosh import scoring
from whoosh.index import open_dir
from whoosh import qparser
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1126, 879)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(40, 30, 100, 30))
self.pushButton.setObjectName("pushButton")
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setGeometry(QtCore.QRect(180, 30, 120, 30))
self.pushButton_2.setObjectName("pushButton_2")
self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_3.setGeometry(QtCore.QRect(620, 30, 80, 30))
self.pushButton_3.setObjectName("pushButton_3")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(380, 60, 191, 21))
self.lineEdit.setObjectName("lineEdit")
self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit_2.setGeometry(QtCore.QRect(40, 90, 50, 21))
self.lineEdit_2.setObjectName("lineEdit_2")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(380, 30, 50, 35))
font = QtGui.QFont()
font.setPointSize(10)
self.label.setFont(font)
self.label.setObjectName("label")
self.label2 = QtWidgets.QLabel(self.centralwidget)
self.label2.setGeometry(QtCore.QRect(40, 70, 150, 16))
font = QtGui.QFont()
font.setPointSize(10)
self.label2.setFont(font)
self.label2.setObjectName("label")
self.tableView = QtWidgets.QTableView(self.centralwidget)
self.tableView.setGeometry(QtCore.QRect(0, 120, 1121, 721))
self.tableView.setObjectName("tableView")
self.horizontal_header = self.tableView.horizontalHeader()
self.vertical_header = self.tableView.verticalHeader()
self.horizontal_header.setSectionResizeMode(
QHeaderView.ResizeToContents
)
self.vertical_header.setSectionResizeMode(
QHeaderView.ResizeToContents
)
self.horizontal_header.setStretchLastSection(True)
self.tableView.showGrid()
self.tableView.wordWrap()
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1126, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
self.pushButton.clicked.connect(self.open_directory)
self.pushButton_2.clicked.connect(self.createindex)
self.pushButton_3.clicked.connect(self.export)
self.lineEdit.returnPressed.connect(self.search)
def open_directory(self):
self.dialog = QtWidgets.QFileDialog()
self.folder_path = self.dialog.getExistingDirectory(None, "Select Folder")
return self.folder_path
def createindex(self):
os.chdir(self.folder_path)
self.mypdfiles = glob.glob("*.pdf")
#creation of folder for splitted files
MYDIR = ("Splitted")
CHECK_FOLDER = os.path.isdir(MYDIR)
if not CHECK_FOLDER:
os.makedirs(MYDIR)
# save split downloaded file and save into new folder
for self.file in self.mypdfiles:
self.fname = os.path.splitext(os.path.basename(self.file))[0]
self.pdf = PdfFileReader(self.file)
for self.page in range(self.pdf.getNumPages()):
self.pdfwrite = PdfFileWriter()
self.pdfwrite.addPage(self.pdf.getPage(self.page))
self.outputfilename = '{}_page_{}.pdf'.format(self.fname, self.page+1)
with open(os.path.join("./Splitted", self.outputfilename), 'wb') as out:
self.pdfwrite.write(out)
print('Created: {}'.format(self.outputfilename))
#set working directory
os.chdir(self.folder_path + "/Splitted")
self.spltittedfiles = glob.glob("*.pdf")
MYDIR = ("Txt")
CHECK_FOLDER = os.path.isdir(MYDIR)
if not CHECK_FOLDER:
os.makedirs(MYDIR)
# Load your PDF
for self.file in self.spltittedfiles:
with open(self.file, "rb") as f:
self.pdf = pdftotext.PDF(f)
#creation of folder for splitted files
# Save all text to a txt file.
with open(os.path.join("./TXT", os.path.splitext(os.path.basename(self.file))[0] + ".txt") , 'w', encoding = 'utf-8') as f:
f.write("\n\n".join(self.pdf))
f.close()
os.chdir(self.folder_path)
MYDIR = ("indexdir")
CHECK_FOLDER = os.path.isdir(MYDIR)
if not CHECK_FOLDER:
os.makedirs(MYDIR)
self.my_analyzer = RegexTokenizer()| StopFilter(lang = "en")
self.schema = Schema(title=TEXT(stored=True),path=ID(stored=True),
content=TEXT(analyzer = self.my_analyzer),
textdata=TEXT(stored=True))
# set an index writer to add document as per schema
self.ix = index.create_in("indexdir",self.schema)
self.writer = self.ix.writer()
self.filepaths = [os.path.join("./Splitted/Txt",i) for i in os.listdir("./Splitted/Txt")]
for path in self.filepaths:
self.fp = open(path, "r", encoding='utf-8')
self.text = self.fp.read()
self.writer.add_document(title = os.path.splitext(os.path.basename(path))[0] , path=path, content=self.text,textdata=self.text)
self.fp.close()
self.writer.commit()
def search(self):
os.chdir(self.folder_path)
self.ix = open_dir("indexdir")
MYDIR = ("Results")
CHECK_FOLDER = os.path.isdir(MYDIR)
if not CHECK_FOLDER:
os.makedirs(MYDIR)
self.text = self.lineEdit.text()
self.query_str = self.text
self.query = qparser.QueryParser("textdata", schema = self.ix.schema)
self.q = self.query.parse(self.query_str)
self.topN = self.lineEdit_2.text()
if self.lineEdit_2.text() == "":
self.topN = 1000
else:
self.topN = int(self.lineEdit_2.text())
with self.ix.searcher(weighting=scoring.Frequency) as searcher:
self.results = searcher.search(self.q, terms=True, limit=self.topN)
for self.i in range(self.topN):
print(self.results[self.i]['title'], self.results[self.i]['textdata'])
def export(self):
with self.ix.searcher(weighting=scoring.Frequency) as searcher:
self.results = searcher.search(self.q, terms=True, limit= None)
for self.i in range(self.topN):
with open(os.path.join(self.folder_path, self.text + ".txt"), 'a') as f:
print(self.results[self.i]['title'], self.results[self.i]['textdata'], file=f)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Search Text"))
self.pushButton.setText(_translate("MainWindow", "Select Folder"))
self.pushButton_2.setText(_translate("MainWindow", "Create Database"))
self.pushButton_3.setText(_translate("MainWindow", "Export"))
self.label.setText(_translate("MainWindow", "Search"))
self.label2.setText(_translate("MainWindow", "Top Results"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
What I want to do now is to show the results also in the table. I have been trying to understand how to "send" the returned value of the search function to the table and how to show it. It should have two columns: File_Page and Content and as many rows as top results selected. Each row should then show the file with hit and the text of interest like this:
So far I have been able to just set to parameter of the table, but not much more.
Is there any mean to let the table how the results without pushing any other button? As I have so far understood, is it possible to trigger the same function from different places of the code but I did not find the opposite, that is to activate two functions with just one signal
I have found lots of examples but none suits the objective. I am still learning how to use Python and I have never used C++.
Use other signal to trigger layoutChanged. i.e. QLineEdit's signal.
Mind me if I understood your question wrong, assuming you want to update search results as soon as you type something in Search field.
In that case, here's neat working example demonstrating immediate searching on 5 entries:
from PySide2.QtWidgets import QWidget, QApplication, QPushButton, QVBoxLayout, QLineEdit
from PySide2.QtCore import QAbstractTableModel, QModelIndex, Qt, QObject
from PySide2.QtWidgets import QTableView
import sys
class Table(QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def data(self, index: QModelIndex, role: int = ...):
if role == Qt.DisplayRole:
return self._data[index.row()][index.column()]
def rowCount(self, parent: QModelIndex = ...) -> int:
return len(self._data)
def columnCount(self, parent: QModelIndex = ...) -> int:
return len(self._data[0])
def overWriteData(self, new_list):
self._data = new_list
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.table = QTableView()
self.line = QLineEdit()
self.layout = QVBoxLayout()
self.layout.addWidget(self.table)
self.layout.addWidget(self.line)
self.data = [('stack overflow', 'some_fancy_data'),
('stack overflow', 'some_fancy_data'),
('stack underflow', 'some_fancy_data'),
('Server Fault', 'some_fancy_data'),
('Ask Ubuntu', 'some_fancy_data')]
self.model = Table(self.data)
self.table.setModel(self.model)
self.setLayout(self.layout)
self.line.textChanged.connect(self.update)
def update(self):
filtered = [i for i in self.data if self.line.text() in i[0]]
if filtered:
self.model.overWriteData(filtered)
self.model.layoutChanged.emit()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
I have shifted to Qtable Widget, created a new function (datatable) and called it from setup UI.
This is the new setupUI:
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1126, 879)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(40, 30, 100, 30))
self.pushButton.setObjectName("pushButton")
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setGeometry(QtCore.QRect(180, 30, 120, 30))
self.pushButton_2.setObjectName("pushButton_2")
self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_3.setGeometry(QtCore.QRect(620, 30, 80, 30))
self.pushButton_3.setObjectName("pushButton_3")
self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_4.setGeometry(QtCore.QRect(180, 80, 80, 30))
self.pushButton_4.setObjectName("pushButton_4")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(380, 60, 191, 21))
self.lineEdit.setObjectName("lineEdit")
self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit_2.setGeometry(QtCore.QRect(40, 90, 50, 21))
self.lineEdit_2.setObjectName("lineEdit_2")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(380, 30, 50, 35))
font = QtGui.QFont()
font.setPointSize(10)
self.label.setFont(font)
self.label.setObjectName("label")
self.label2 = QtWidgets.QLabel(self.centralwidget)
self.label2.setGeometry(QtCore.QRect(40, 70, 150, 16))
font = QtGui.QFont()
font.setPointSize(10)
self.label2.setFont(font)
self.label2.setObjectName("label")
self.tableWidget = QtWidgets.QTableWidget(self.centralwidget)
self.tableWidget.setGeometry(QtCore.QRect(0, 120, 1121, 721))
self.tableWidget.setObjectName("tableWidget")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1126, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
self.pushButton.clicked.connect(self.open_directory)
self.pushButton_2.clicked.connect(self.createindex)
self.pushButton_3.clicked.connect(self.export)
self.pushButton_4.clicked.connect(self.OCR)
self.lineEdit.returnPressed.connect(self.search)
self.lineEdit.returnPressed.connect(self.datatable)
And this is the function that takes the data returned from another function within the class.
numrows = len(self.data)
numcols = len(self.data[0])
self.tableWidget.setColumnCount(numcols)
self.tableWidget.setRowCount(numrows)
self.tableWidget.setHorizontalHeaderLabels((list(self.data[0].keys())))
self.tableWidget.resizeColumnsToContents()
self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
self.tableWidget.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
for row in range(numrows):
for column in range(numcols):
item = (list(self.data[row].values())[column])
self.tableWidget.setItem(row, column, QTableWidgetItem(item)) ```

VTKWidget in Qt is not updated as expected

I would like to display a 3D-Animation in my Qt5-Gui. Everything works as expected, but unfortunately the scene is not getting updated when I don't interact with the vtkWidget. In other words: When I want to see the animation, I need to click continously with the mouse on the widget. I'd be greatful for any help.
import sys
import vtk
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import Qt
import numpy as np
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
class mainWindow(Qt.QMainWindow):
def __init__(self, parent = None):
Qt.QMainWindow.__init__(self, parent)
self.frame = Qt.QFrame()
self.vl = Qt.QVBoxLayout()
self.button = QtWidgets.QPushButton("TestButton")
self.label = QtWidgets.QLabel("This is a label")
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
#Create Source
self.source = vtk.vtkCylinderSource()
self.source.SetCenter(0, 0, 0)
self.source.SetRadius(5.0)
#Create Mapper
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(self.source.GetOutputPort())
#Create Actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(self.mapper)
#Create poke matrix for cylinder
self.pMatrix = vtk.vtkMatrix4x4()
self.vl.addWidget(self.vtkWidget)
self.vl.addWidget(self.button)
self.vl.addWidget(self.label)
self.ren = vtk.vtkRenderer()
self.ren.AddActor(self.actor)
self.renWin = self.vtkWidget.GetRenderWindow()
self.renWin.AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
#Settings
self.ren.SetBackground(0.2, 0.2, 0.2)
self.timeStep = 20 #ms
self.total_t = 0
#Inititalize Window, Interactor, Renderer, Layout
self.frame.setLayout(self.vl)
self.setCentralWidget(self.frame)
self.ren.ResetCamera()
self.show()
self.iren.Initialize()
# Create Timer
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.timerCallback)
self.timer.start(self.timeStep)
def timerCallback(self, *args):
self.total_t += self.timeStep / 1000
#Rotate Cylinder
angle = 2 * np.pi * self.total_t
rotMatrix = np.array([[np.cos(angle), -np.sin(angle), 0],
[np.sin(angle), np.cos(angle), 0],
[0, 0, 1]])
for i in range(3):
for j in range(3):
self.pMatrix.SetElement(i, j, rotMatrix[i, j])
self.actor.PokeMatrix(self.pMatrix)
self.ren.Render()
if __name__ == "__main__":
app = Qt.QApplication(sys.argv)
window = mainWindow()
sys.exit(app.exec_())
After reading the paintEvent()-Method of this file, I managed to find out, that one needs to call the Render()-Method of the Interactor-object. So instead of self.ren.Render() one needs to call self.iren.Render(). Then everything works.
Complete example code:
import sys
import vtk
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import Qt
import numpy as np
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
class mainWindow(Qt.QMainWindow):
def __init__(self, parent = None):
Qt.QMainWindow.__init__(self, parent)
self.frame = Qt.QFrame()
self.vl = Qt.QVBoxLayout()
self.button = QtWidgets.QPushButton("TestButton")
self.label = QtWidgets.QLabel("This is a label")
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
#Create Source
self.source = vtk.vtkCylinderSource()
self.source.SetCenter(0, 0, 0)
self.source.SetRadius(5.0)
#Create Mapper
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(self.source.GetOutputPort())
#Create Actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(self.mapper)
#Create poke matrix for cylinder
self.pMatrix = vtk.vtkMatrix4x4()
self.vl.addWidget(self.vtkWidget)
self.vl.addWidget(self.button)
self.vl.addWidget(self.label)
self.ren = vtk.vtkRenderer()
self.ren.AddActor(self.actor)
self.renWin = self.vtkWidget.GetRenderWindow()
self.renWin.AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
#Settings
self.ren.SetBackground(0.2, 0.2, 0.2)
self.timeStep = 20 #ms
self.total_t = 0
#Inititalize Window, Interactor, Renderer, Layout
self.frame.setLayout(self.vl)
self.setCentralWidget(self.frame)
self.ren.ResetCamera()
self.show()
self.iren.Initialize()
# Create Timer
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.timerCallback)
self.timer.start(self.timeStep)
def timerCallback(self, *args):
self.total_t += self.timeStep / 1000
#Rotate Cylinder
angle = 2 * np.pi * self.total_t
rotMatrix = np.array([[np.cos(angle), -np.sin(angle), 0],
[np.sin(angle), np.cos(angle), 0],
[0, 0, 1]])
for i in range(3):
for j in range(3):
self.pMatrix.SetElement(i, j, rotMatrix[i, j])
self.actor.PokeMatrix(self.pMatrix)
self.iren.Render() #NOT: self.ren.Render()
if __name__ == "__main__":
app = Qt.QApplication(sys.argv)
window = mainWindow()
sys.exit(app.exec_())
Thanks #cakelover, I was facing same issue but in C++, your solution helped me resolve it:
//PCLVisualizer pointer
pcl::visualization::PCLVisualizer::Ptr viewer_3D;
//Renderer method to update the visualizer
viewer_3D->getRenderWindow()->GetInteractor()->Render();

How to delete old plot on Matplotlib figure embedded in PyQt5?

I am using matplotlib figure embedded in pyqt5 to draw a frame taking the height and the width from line edit entries.It works well but when I change the values inside the line edits and click on the push button it will draw another frame over the previous one. I have tried plt.gcf().clear() and ax.clear and they did not work.
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Draw Frame")
self.setGeometry(100,100,680, 450)
# Creation of figure and canvas
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111)
self.ax.axis("off")
#self.toolbar = NavigationToolbar(self.canvas, self)
self.plot_widget = QWidget(self)
self.plot_widget.setGeometry(250, 10, 400, 400)
plot_box = QVBoxLayout()
plot_box.addWidget(self.canvas)
self.plot_widget.setLayout(plot_box)
self.label1=QLabel("Frame height",self)
self.label1.move(10,30)
self.label2 = QLabel("Frame height", self)
self.label2.move(10, 70)
self.lineEdit1=QLineEdit(self)
self.lineEdit1.move(100,30)
self.lineEdit1.setText("10")
self.lineEdit2 = QLineEdit(self)
self.lineEdit2.move(100, 70)
self.lineEdit2.setText("20")
self.button = QPushButton('Draw Frame', self)
self.button.clicked.connect(self.plot)
self.button.move(70, 350)
self.show()
def plot(self):
if len(self.lineEdit1.text())!=0:
self.h=int(self.lineEdit1.text())
else:
self.h=0
if len(self.lineEdit2.text()) != 0:
self.w=int(self.lineEdit2.text())
else:
self.w=0
x = [0, 0, self.w, self.w]
y = [0, self.h, self.h, 0]
self.ax.plot(x, y)
self.canvas.draw()
Picture of the app
Instead of clearing the axes you may update the line that is drawn with the new coordinates.
from PyQt5.QtWidgets import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Draw Frame")
self.setGeometry(100,100,680, 450)
# Creation of figure and canvas
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111)
self.ax.axis("off")
self.line, = self.ax.plot([])
self.ax.axis([0,100,0,100])
#self.toolbar = NavigationToolbar(self.canvas, self)
self.plot_widget = QWidget(self)
self.plot_widget.setGeometry(250, 10, 400, 400)
plot_box = QVBoxLayout()
plot_box.addWidget(self.canvas)
self.plot_widget.setLayout(plot_box)
self.label1=QLabel("Frame height",self)
self.label1.move(10,30)
self.label2 = QLabel("Frame height", self)
self.label2.move(10, 70)
self.lineEdit1=QLineEdit(self)
self.lineEdit1.move(100,30)
self.lineEdit1.setText("10")
self.lineEdit2 = QLineEdit(self)
self.lineEdit2.move(100, 70)
self.lineEdit2.setText("20")
self.button = QPushButton('Draw Frame', self)
self.button.clicked.connect(self.plot)
self.button.move(70, 350)
self.show()
def plot(self):
if len(self.lineEdit1.text())!=0:
self.h=int(self.lineEdit1.text())
else:
self.h=0
if len(self.lineEdit2.text()) != 0:
self.w=int(self.lineEdit2.text())
else:
self.w=0
x = [0, 0, self.w, self.w]
y = [0, self.h, self.h, 0]
self.line.set_data(x,y)
self.canvas.draw()
if __name__=='__main__':
app = QApplication(sys.argv)
w = Window()
app.exec_()
As mentioned in the comment above, you need to call self.ax.clear() every time you click the button. Otherwise you will be redrawing on the same plot. Not sure why your graph doesnt reset on clear(), but here is the code I ran which worked fine. Let me know if it works for you:
from PyQt5.QtWidgets import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Draw Frame")
self.setGeometry(100,100,680, 450)
# Creation of figure and canvas
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111)
self.ax.axis("off")
#self.toolbar = NavigationToolbar(self.canvas, self)
self.plot_widget = QWidget(self)
self.plot_widget.setGeometry(250, 10, 400, 400)
plot_box = QVBoxLayout()
plot_box.addWidget(self.canvas)
self.plot_widget.setLayout(plot_box)
self.label1=QLabel("Frame height",self)
self.label1.move(10,30)
self.label2 = QLabel("Frame height", self)
self.label2.move(10, 70)
self.lineEdit1=QLineEdit(self)
self.lineEdit1.move(100,30)
self.lineEdit1.setText("10")
self.lineEdit2 = QLineEdit(self)
self.lineEdit2.move(100, 70)
self.lineEdit2.setText("20")
self.button = QPushButton('Draw Frame', self)
self.button.clicked.connect(self.plot)
self.button.move(70, 350)
self.show()
def plot(self):
self.ax.clear()
if len(self.lineEdit1.text())!=0:
self.h=int(self.lineEdit1.text())
else:
self.h=0
if len(self.lineEdit2.text()) != 0:
self.w=int(self.lineEdit2.text())
else:
self.w=0
x = [0, 0, self.w, self.w]
y = [0, self.h, self.h, 0]
self.ax.plot(x, y)
self.canvas.draw()
if __name__=='__main__':
app = QApplication(sys.argv)
w = Window()
app.exec_()

How to scale background image to cover MainWindow in PyQt5

I trying to learn PyQt5 so it might a noobie question, but I search a lot and I couldn't find a solution.
I wrinting a sample code showing a countdow to a defined datetime and now it looks like in the image below:
I need to resize the image to cover the app, atm I have a window 300x200 and background image 2400x1800.
I also need the background to resize on MainWindow resize. At first glance css property background-size is not supported, border-image would strech the image.
OS: Windows 7-10 (not a choise)
Python: 3.4.3
PyQt: 5.5
Current code (not working)
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import QObject, pyqtSignal, QTimer
import design
import sys
from datetime import datetime, timedelta
# UI base class is inherited from design.Ui_MainWindow
class Counter(QtWidgets.QMainWindow, design.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
# applying background image
self.background = QtGui.QPixmap("./assets/img/trecime.jpg")
palette = QtGui.QPalette()
palette.setBrush(QtGui.QPalette.Background,QtGui.QBrush(self.background).scale(self.size()))
self.MainWindow.setPalette(palette)
# custom colors for QLCDWidgets
lcdPalette = self.dayCount.palette()
lcdPalette.setColor(lcdPalette.WindowText, QtGui.QColor(255,0,0, 200))
lcdPalette.setColor(lcdPalette.Background, QtGui.QColor(0, 0, 0, 0))
lcdPalette.setColor(lcdPalette.Light, QtGui.QColor(200, 0, 0, 120))
lcdPalette.setColor(lcdPalette.Dark, QtGui.QColor(100, 0, 0, 150))
self.dayCount.setPalette(lcdPalette)
lcdPalette.setColor(lcdPalette.WindowText, QtGui.QColor(255,255,255, 200))
lcdPalette.setColor(lcdPalette.Light, QtGui.QColor(200, 0, 0, 0))
lcdPalette.setColor(lcdPalette.Dark, QtGui.QColor(200, 0, 0, 0))
self.timeCount.setPalette(lcdPalette)
# init Qtimer
self.timer = QTimer()
self.timer.timeout.connect(self.update_timer)
self.timer.start(200)
# config final date
self.finalDatetime = datetime(2016, 11, 30, 14, 00)
def resizeEvent(self, resizeEvent):
print('resized')
def update_timer(self):
currentDatetime = datetime.today()
delta = self.finalDatetime - currentDatetime
(days, hours, minutes) = days_hours_minutes(delta)
self.dayCount.display(days)
# blinking colon
separator = ":" if delta.microseconds < 799999 else ' '
self.timeCount.display('%02d%s%02d' % (hours, separator, minutes))
def days_hours_minutes(td):
return td.days, td.seconds//3600, (td.seconds//60)%60
def main():
app = QtWidgets.QApplication(sys.argv)
counter = Counter()
counter.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
pyqt style sheet can be used for this resizing
try
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self,MainWindow):
super().__init__()
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
MainWindow.setObjectName("MainWindow")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.centralWidget)
self.horizontalLayout_2.setContentsMargins(11, 11, 11, 11)
self.horizontalLayout_2.setSpacing(6)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSpacing(6)
self.horizontalLayout.setObjectName("horizontalLayout")
self.start_button = QtWidgets.QPushButton(self.centralWidget)
self.start_button.setObjectName("start_button")
self.horizontalLayout.addWidget(self.start_button)
self.stop_button = QtWidgets.QPushButton(self.centralWidget)
self.stop_button.setObjectName("stop_button")
self.horizontalLayout.addWidget(self.stop_button)
self.horizontalLayout_2.addLayout(self.horizontalLayout)
MainWindow.setCentralWidget(self.centralWidget)
self.menuBar = QtWidgets.QMenuBar(MainWindow)
self.menuBar.setGeometry(QtCore.QRect(0, 0, 400, 26))
self.menuBar.setObjectName("menuBar")
MainWindow.setMenuBar(self.menuBar)
self.mainToolBar = QtWidgets.QToolBar(MainWindow)
self.mainToolBar.setObjectName("mainToolBar")
MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.mainToolBar)
self.statusBar = QtWidgets.QStatusBar(MainWindow)
self.statusBar.setObjectName("statusBar")
MainWindow.setStatusBar(self.statusBar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.start_button.setText(_translate("MainWindow", "Start"))
self.stop_button.setText(_translate("MainWindow", "Stop"))
stylesheet = """
MainWindow {
border-image: url("The_Project_logo.png");
background-repeat: no-repeat;
background-position: center;
}
"""
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
app.setStyleSheet(stylesheet) # <---
window = MainWindow()
window.resize(640, 640)
window.show()
sys.exit(app.exec_())