Issue
I am trying to plot some data in CLI. I am looking to plot multiple charts in separate window and it should not block CLI so that I can still give commands in CLI.
class CLI(cmd.Cmd):
def __init__(self):
super().__init__()
def do_plot(self):
# plot here
if __name__ == '__main__':
CLI().cmdloop()
method 1: using matplotlib
I came to know that matplotlib is not thread safe.
method 2: pyqtgraph
I have asked a detail question here : https://forum.qt.io/topic/122033/running-multiple-plots-with-pyside2, still no solution.
Only solution I can think of now is that to write separate program and connect it with CLI using socket and share data. Is there any easy and efficient way to do it?
Solution
Qt widgets are not thread-safe so in general no widget can (or should) be created and modified in another thread (this involves the Qt backend of matplotlib and pyqtgraph).
Considering the above, a possible alternative is that in the main thread the Qt eventloop lives and in a secondary thread the cmd.Cmd eventloop, and then send the information through signals that are thread-safe.
import cmd
import signal
import sys
import threading
from PyQt5 import QtCore, QtWidgets
import numpy as np
from matplotlib.backends.backend_qt5agg import (
FigureCanvas,
NavigationToolbar2QT as NavigationToolbar,
)
from matplotlib.figure import Figure
class QtSignaller(QtCore.QObject):
commandChanged = QtCore.pyqtSignal(str, object)
class QtShell(cmd.Cmd):
def __init__(self, qobject):
super().__init__()
self._qobject = qobject
@property
def qobject(self):
return self._qobject
def do_plot(self, arg):
self.qobject.commandChanged.emit("plot", parse(arg))
class QtManager(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._window = None
@QtCore.pyqtSlot(str, object)
def run_command(self, command, parameters):
x, y = parameters, np.sin(parameters)
if self._window is None:
self._window = FigureCanvas(Figure(figsize=(5, 3)))
self._ax = self._window.figure.add_subplot(111)
self._ax.plot(x, y)
else:
self._ax.clear()
self._ax.plot(x, y)
self._ax.figure.canvas.draw()
self._window.show()
def run_command(signaller):
shell = QtShell(signaller)
shell.cmdloop()
def parse(arg):
return tuple(map(float, arg.split()))
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtWidgets.QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
signaller = QtSignaller()
manager = QtManager()
signaller.commandChanged.connect(manager.run_command)
threading.Thread(target=run_command, args=(signaller,), daemon=True).start()
app.exec_()
if __name__ == "__main__":
main()
Note: Although my example uses matplotlib the same logic should be used for pyqtgraph
Answered By - eyllanesc
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.