在 python cmd.Cmd 中绘制图表
Plotting charts in python cmd.Cmd
我正在尝试在 CLI 中绘制一些数据。我希望在单独的 window 中绘制多个图表,它不应该阻止 CLI,这样我仍然可以在 CLI 中发出命令。
class CLI(cmd.Cmd):
def __init__(self):
super().__init__()
def do_plot(self):
# plot here
if __name__ == '__main__':
CLI().cmdloop()
方法一:使用matplotlib
我开始知道 matplotlib 不是线程安全的。
方法二:pyqtgraph
我在这里问了一个详细的问题:https://forum.qt.io/topic/122033/running-multiple-plots-with-pyside2,仍然没有解决方案。
我现在唯一能想到的解决方案是单独编写程序并使用套接字将其与CLI连接并共享数据。有什么简单有效的方法吗?
Qt 小部件不是线程安全的,因此通常不能(或不应该)在另一个线程中创建和修改任何小部件(这涉及 matplotlib 和 pyqtgraph 的 Qt 后端)。
考虑到上述情况,一个可能的替代方案是在主线程中使用 Qt 事件循环,在辅助线程中使用 cmd.Cmd 事件循环,然后通过线程安全的信号发送信息。
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()
注意:虽然我的示例使用了matplotlib,但pyqtgraph应该使用相同的逻辑
我正在尝试在 CLI 中绘制一些数据。我希望在单独的 window 中绘制多个图表,它不应该阻止 CLI,这样我仍然可以在 CLI 中发出命令。
class CLI(cmd.Cmd):
def __init__(self):
super().__init__()
def do_plot(self):
# plot here
if __name__ == '__main__':
CLI().cmdloop()
方法一:使用matplotlib
我开始知道 matplotlib 不是线程安全的。
方法二:pyqtgraph
我在这里问了一个详细的问题:https://forum.qt.io/topic/122033/running-multiple-plots-with-pyside2,仍然没有解决方案。
我现在唯一能想到的解决方案是单独编写程序并使用套接字将其与CLI连接并共享数据。有什么简单有效的方法吗?
Qt 小部件不是线程安全的,因此通常不能(或不应该)在另一个线程中创建和修改任何小部件(这涉及 matplotlib 和 pyqtgraph 的 Qt 后端)。
考虑到上述情况,一个可能的替代方案是在主线程中使用 Qt 事件循环,在辅助线程中使用 cmd.Cmd 事件循环,然后通过线程安全的信号发送信息。
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()
注意:虽然我的示例使用了matplotlib,但pyqtgraph应该使用相同的逻辑