Home > Software design >  Fatal Python Error when dragging matplotlib plot if PyQt window is opened
Fatal Python Error when dragging matplotlib plot if PyQt window is opened

Time:01-12

I've ran into following problem. (Python 3.8) I made matplotlib scatterplot with 2 GUI buttons. In separate module I made PyQt5 GUI app that I'm hoping to be able to use as GUI interface to let users modify generated plot in comfortable way using widgets to set various plot parameters. I've bound PyQt5 GUI window opening to one of the buttons on plot. When PyQT GUI window remains opened I can't interact with plot window. Even more, If I try to drag plot window, program crashes with:

Fatal Python error: PyEval_RestoreThread: NULL tstate

Python runtime state: initialized

Current thread 0x00000674 (most recent call first)

Folowing some paths to Python libraries...

Here's example code that simulates my program and generates the same issue:

Plot: my_plot.py

from matplotlib.widgets import Button
import random
import my_qt_app

def MyPlot():
    data_x = [random.randint(-100, 100) for n in range(20)]
    data_y = [random.randint(-100, 100) for n in range(20)]

    plt.scatter(data_x, data_y)
    #some irrelevant bs data to put on plot
    ax_button_1 = plt.axes([0.68, 0.01, 0.12, 0.05])
    ax_button_2 = plt.axes([0.81, 0.01, 0.1, 0.05])
    button_1 = Button(ax_button_1, "TXT")
    button_2 = Button(ax_button_2, "Qt_GUI")
    button_1.on_clicked(lambda event: SaySomething(event))
    button_2.on_clicked(lambda event: my_qt_app.OpenPyQtGUI(event))

    plt.show()

def SaySomething(event):
    print(["SOMTHING" for n in range(10)])

MyPlot()

... and the PyQt5 gui app: my_qt_app.py

#common
from PyQt5.QtWidgets import QApplication, QWidget
#layouts
from PyQt5.QtWidgets import QGridLayout, QHBoxLayout, QGroupBox
#widgets
from PyQt5.QtWidgets import QLabel, QPushButton, QComboBox, QCheckBox

class PlotEditor(QWidget):
    def __init__(self):
        super().__init__()

        self.initGUI()

    def initGUI(self):
        #example gui elements
        self.main_grid = QGridLayout()
        self.setLayout(self.main_grid)
        self.setWindowTitle("test window")
        self.groupbox_1 = QGroupBox()
        self.groupbox_1.setCheckable(True)
        group_1_layout = QHBoxLayout()
        self.groupbox_1.setLayout(group_1_layout)

        group_1_layout.addWidget(QLabel("#placeholder1"))
        group_1_layout.addWidget(QPushButton("my_button"))

        self.groupbox_2 = QGroupBox()
        self.groupbox_2.setCheckable(True)
        group_2_layout = QHBoxLayout()
        self.groupbox_2.setLayout(group_2_layout)
        combo_1 = QComboBox()
        group_2_layout.addWidget(QComboBox(combo_1))
        group_2_layout.addWidget(QCheckBox("toggle"))

        self.main_grid.addWidget(self.groupbox_1, 0, 0)
        self.main_grid.addWidget(self.groupbox_2, 1, 0)

# called in my_plot.py
def OpenPyQtGUI(event):
    app = QApplication(sys.argv)
    gui_object = PlotEditor()
    gui_object.show()
    app.exec()

Setting matplotlib backend to: matplotlib.use('Qt5Agg') Causes The event loop is already running error.

Any suggestions/help much appreciated.

CodePudding user response:

Creating the Qt application and starting its loop results in making any of its widget modal to the plot.

What you could do is to use the "Qt5Agg" engine, create the widget in the function and then call show() when the button is clicked:

matplotlib.use('Qt5Agg')

# ...

def MyPlot():
    # ...
    button_1.on_clicked(lambda event: SaySomething(event))

    gui_object = PlotEditor()
    button_2.on_clicked(lambda *args: gui_object.show())

    plt.show()

If you need to use the widget to change aspects of the plot, then I suggest you to add a QDialogButtonBox with a basic Ok button, and connect its accepted signal to a function that updates the plot.

def MyPlot():
    # ...
    button_1.on_clicked(lambda event: SaySomething(event))

    gui_object = PlotEditor()
    button_2.on_clicked(lambda *args: gui_object.show())
    gui_object.accepted.connect(lambda: doSomething(gui_object))

    plt.show()

def doSomething(gui_object):
    print(gui_object.check_1.isChecked())


class PlotEditor(QDialog):
    # ...
    def initGUI(self):
        #example gui elements
        self.main_grid = QGridLayout(self)
        self.setWindowTitle("test window")
        self.groupbox_1 = QGroupBox()
        self.groupbox_1.setCheckable(True)
        group_1_layout = QHBoxLayout(self.groupbox_1)

        group_1_layout.addWidget(QLabel("#placeholder1"))
        group_1_layout.addWidget(QPushButton("my_button"))

        self.groupbox_2 = QGroupBox()
        self.groupbox_2.setCheckable(True)
        group_2_layout = QHBoxLayout()
        self.groupbox_2.setLayout(group_2_layout)
        self.combo_1 = QComboBox() # <- instance attribute
        group_2_layout.addWidget(self.combo_1)
        self.check_1 = QCheckBox("toggle") # <- instance attribute
        group_2_layout.addWidget(self.check_1)

        self.main_grid.addWidget(self.groupbox_1, 0, 0)
        self.main_grid.addWidget(self.groupbox_2, 1, 0)

        buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
        self.main_grid.addWidget(buttonBox)
        self.accepted = buttonBox.accepted
        self.accepted.connect(self.hide)

Note: creating a combo as a child of another combo is both wrong and pointless (see group_2_layout.addWidget(QComboBox(combo_1))).
Also, if you need those widgets to get the configuration you must create instance attributes for them as I did above.

  •  Tags:  
  • Related