Reputation: 37
I'm looking for a solution to solve the following issue: My program starts with a plot of all data, later when I start a function a worker is plotting the same graph according to it times. So there are two lines, first a red one that shows how the plot will look like, later the plot that follows the first graph, done by a worker.
Unfortunately, the second plot is very thin. I've created a variable called "plotsize" in my example. This can change the first plot, but I have no idea how to change the characteristics of the second one within the threading with a worker. I'm using QThread.
Here my example codes, two data. Name of the GUI file is just GUI.py
#GUI.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(739, 532)
MainWindow.setStyleSheet("")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.WidgetPlot = PlotWidget(self.centralwidget)
self.WidgetPlot.setGeometry(QtCore.QRect(100, 40, 541, 341))
self.WidgetPlot.setObjectName("WidgetPlot")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(330, 420, 93, 28))
self.pushButton.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
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", "Main Window"))
self.pushButton.setText(_translate("MainWindow", "Start"))
from pyqtgraph import PlotWidget
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_())
And here the script:
#PROGRAM/SCRIPT
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication
import sys
import GUI
import datetime
import pyqtgraph
import time
plotsize = 2
# =============================================================================
# Threading for not freezing the GUI while running
# =============================================================================
class Worker(QtCore.QObject):
progress = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
widgetplot = QtCore.pyqtSignal(list, list)
def __init__(self):
super().__init__()
def start(self):
self._run()
def _run(self):
self._count = 0
self.x = plot_time[:0]
self.y = plot_value[:0]
self.widgetplot.emit(self.x, self.y)
self._start_time = datetime.datetime.now()
while 0 <= self._count < 100:
self._count_prev = self._count
QtCore.QThread.usleep(10000)
self._diff = datetime.datetime.now() - self._start_time
self._count = int((self._diff.total_seconds() * 10))
if(self._count != self._count_prev):
print(self._count)
self.x = plot_time[:self._count]
self.y = plot_value[:self._count]
self.widgetplot.emit(self.x, self.y)
class my_class(QtWidgets.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(my_class, self).__init__(parent)
self.setupUi(self)
self.thread = QtCore.QThread(self)
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.start)
self.worker.finished.connect(self.thread.quit)
self.worker.widgetplot.connect(self.WidgetPlot.plot)
self.pushButton.clicked.connect(self.my_function)
self.WidgetPlot.setMouseEnabled(x=False, y=False)
font=QtGui.QFont()
font.setPixelSize(20)
font.setBold(True)
self.WidgetPlot.getAxis("bottom").setTickFont(font)
self.WidgetPlot.getAxis("left").setTickFont(font)
def my_function(self):
global plot_time
plot_time = []
global plot_value
plot_value = []
for i in range(100):
plot_time.append(i)
plot_value.append(i)
self.start()
def preview_plot(self):
self.WidgetPlot.clear()
self.WidgetPlot.setXRange(0, 105, padding=0)
self.WidgetPlot.setYRange(0, 105, padding=0)
self.preview_line = self.WidgetPlot.plot(plot_time, plot_value, pen=pyqtgraph.mkPen('r', width=plotsize))
def start(self):
self.preview_plot()
self.thread.start()
def main():
app = QApplication(sys.argv)
form = my_class()
form.show()
app.exec_()
if __name__ == '__main__':
Many Thanks in advance! Andrew
Upvotes: 0
Views: 192
Reputation: 1412
Based on what I have inferred from your request (i.e. you want just to use two different size for overlapping plot lines) I've rewritten your code with a series of improvements (in my humble opinion).
At the very core of the problem, what I've done is to create a separate pyqtgraph.PlotDataItem
curve, plot it on top of your preview_line
and handle a different size for it (completely randomic, please adjust the size as you desire). Thus, it's all about using
self.second_line.setData(x, y)
instead of recreating it every time with self.WidgetPlot.plot(...)
All the details are into the comments.
#PROGRAM/SCRIPT
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication
import sys
import GUI
import datetime
import pyqtgraph
import time
plotsize = 20
# =============================================================================
# Threading for not freezing the GUI while running
# =============================================================================
class Worker(QtCore.QThread):
progress = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
widgetplot = QtCore.pyqtSignal(list, list)
def __init__(self, plot_time, plot_value):
QtCore.QThread.__init__(self, objectName='WorkerThread')
# AS LONG AS YOU DON'T MODIFY plot_time and plot_value INTO THIS THREAD, they are thread-safe.
# If you intend to modify them in the future (into this thread), you need to implement a mutex/lock system to avoid races
# Note that lists are mutable: you're storing the reference to the actual object that will always be edited into the MAIN THREAD
self.plot_time = plot_time
self.plot_value = plot_value
def run(self):
# This is naturally a LOCAL variable
# self._count = 0
_count = 0
# --- This is useless, it plots ([], [])
# self.widgetplot.emit(self.x, self.y)
# self.x = plot_time[:0]
# self.y = plot_value[:0]
# -----------------------------------
_start_time = datetime.datetime.now()
while 0 <= _count < 100:
# Use local variable!
_count_prev = _count
QtCore.QThread.usleep(10000)
_diff = datetime.datetime.now() - _start_time
_count = int((_diff.total_seconds() * 10))
if(_count != _count_prev):
print(_count)
x = self.plot_time[:_count]
y = self.plot_value[:_count]
# Since plot_time and plot_value are managed by main thread, you would just need to emit _count variable.
# But I'll stick with your code
self.widgetplot.emit(x, y)
class my_class(QtWidgets.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(my_class, self).__init__(parent)
self.setupUi(self)
# In your version, the range is constant irrespective of the data being updated. It can be moved here
self.WidgetPlot.setXRange(0, 105, padding=0)
self.WidgetPlot.setYRange(0, 105, padding=0)
# Create and store ONE line, you will change just the underlying data, not the object itself. WAY MORE efficient
# Different pen with different plot size. Is this what you're seeking?
self.second_line = pyqtgraph.PlotDataItem(pen=pyqtgraph.mkPen('w', width=plotsize*2))
# Here class variable initialization goes
self.plot_time = []
self.plot_value = []
# You don't need `moveToThread`. You can just subclass QThread
self.worker_thread = Worker(self.plot_time, self.plot_value)
# I changed the name just to highlight the fact it is just an update and not a plot rebuilt
self.worker_thread.widgetplot.connect(self.update_second_line_plot)
self.pushButton.clicked.connect(self.my_function)
self.WidgetPlot.setMouseEnabled(x=False, y=False)
font=QtGui.QFont()
font.setPixelSize(20)
font.setBold(True)
self.WidgetPlot.getAxis("bottom").setTickFont(font)
self.WidgetPlot.getAxis("left").setTickFont(font)
def my_function(self):
# Use class variable instead, see the __init__
# ...Not efficient
# for i in range(100):
# plot_time.append(i)
# plot_value.append(i)
# Better:
_l = list(range(100))
self.plot_time.extend(_l)
self.plot_value.extend(_l)
# DON'T DO self.plot_time = list(range(100)) --> it will recreate a new object, but this one is shared with the worker thread!
self.start()
def update_second_line_plot(self, plot_time, plot_value):
# Just update the UNDERLYING data of your curve
self.second_line.setData(plot_time, plot_value)
def start(self):
# First plot preview_plot. done ONCE
self.WidgetPlot.plot(self.plot_time, self.plot_value, pen=pyqtgraph.mkPen('r', width=plotsize))
# Add NOW the new line to be drawn ON TOP of the preview one
self.WidgetPlot.addItem(self.second_line)
# It automatically will do the job. You don't need any plotfunction
self.worker_thread.start()
def main():
app = QApplication(sys.argv)
form = my_class()
form.show()
app.exec_()
if __name__ == '__main__':
main()
The result:
Upvotes: 2
Reputation: 6273
You currently have a signal connected to the plot
method through self.worker.widgetplot.connect(self.WidgetPlot.plot)
, but you'll have a better time if you make a persistent plot - with a specific pen set, at your desired thickness - and then connect your widgetplot
signal to that plot's setData
method.
# ... Ui_MainWindow.__init__
self.plot_curve = self.WidgetPlot.plot(pen=mkPen("w", width=plotsize))
# ... in my_class.__init__
self.worker.widgetplot.connect(self.plot_curve.setData)
Upvotes: 0