2
\$\begingroup\$

I'm trying to find a better approach than my current one to manage the storage of my PyQt application. I have a QTreeWidget where I can add and delete rows. Clicking on these QTreeWidgetItems shows the data linked to it. Each of that linked data consists of a graph with x-y values, and some additional metadata. Currently in the __init__ method I have two empty lists defined:

self.xData = []
self.yData = []

When I load data the x and y values are np.ndarray type, and they get appended to self.xData and self.yData. The metadata is written to a dictionary, the key is the index of the data, value is the metadata. Later I access all of them by index. I've tried to use dictionary the following way: in __init__:

self._dict = {}

The keys were the current index of the row, value is the (x, y) tuple (pseudocode):

self._dict['index of the row'] = (x_values, y_values) 

I think this is still not an elegant way of dealing with the data.

There is the code you can try, but I had to disable and delete huge amount of functionality (including metadata) to reduce its length and to clean it up.

logic.py

import numpy as np
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QAbstractItemView, QFileDialog, QTreeWidgetItem

from ui import Ui_SPP

class SPPWindow(QtWidgets.QMainWindow, Ui_SPP):
    def __init__(self, parent=None):
        super(SPPWindow, self).__init__(parent)
        self.setupUi(self)
        self.xData = []
        self.yData = []
        self.treeWidget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.loadButton.clicked.connect(self.loadData)
        self.treeWidget.itemSelectionChanged.connect(self.previewData)

    def previewData(self):
        """ Function to update the plot."""
        curr = self.treeWidget.currentIndex().row()
        if curr == -1:
            pass
        else:
            Ydata = self.yData[curr]
            Xdata = self.xData[curr]
            self.widget.canvas.axes.clear()
            self.widget.canvas.axes.plot(Xdata, Ydata, 'r')
            self.widget.canvas.axes.grid()
            self.widget.canvas.draw()


    def loadData(self):
        """ Function to load file into QTreeWidget"""
        options = QFileDialog.Options()
        fileName, _ = QFileDialog.getOpenFileName(None,"Load interferogram", "","All Files (*);;Text Files (*.txt)", options=options)
        try:
            if fileName:
                x, y = np.loadtxt(fileName, unpack=True, delimiter=',', usecols=(0, 1))
                self.xData.append(x)
                self.yData.append(y)
                l1 = QTreeWidgetItem([fileName.split('/')[-1]])
                self.treeWidget.addTopLevelItem(l1)
            self.previewData()
        except Exception as e:
            print(e)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    application = SPPWindow()
    application.show()
    sys.exit(app.exec_())

ui.py

# -*- coding: utf-8 -*-

from PyQt5 import QtCore, QtGui, QtWidgets
import numpy as np
from plotwidget import PlotWidget


class Ui_SPP(object):
    def setupUi(self, SPP):
        SPP.setObjectName("SPP")
        SPP.resize(1302, 832)
        self.centralwidget = QtWidgets.QWidget(SPP)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.verticalLayout_3 = QtWidgets.QVBoxLayout()
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.GDSPP = QtWidgets.QLineEdit(self.centralwidget)
        self.GDSPP.setFocusPolicy(QtCore.Qt.NoFocus)
        self.GDSPP.setAlignment(QtCore.Qt.AlignCenter)
        self.GDSPP.setObjectName("GDSPP")
        self.verticalLayout_3.addWidget(self.GDSPP)
        self.GDDSPP = QtWidgets.QLineEdit(self.centralwidget)
        self.GDDSPP.setFocusPolicy(QtCore.Qt.NoFocus)
        self.GDDSPP.setAlignment(QtCore.Qt.AlignCenter)
        self.GDDSPP.setObjectName("GDDSPP")
        self.verticalLayout_3.addWidget(self.GDDSPP)
        self.TODSPP = QtWidgets.QLineEdit(self.centralwidget)
        self.TODSPP.setFocusPolicy(QtCore.Qt.NoFocus)
        self.TODSPP.setAlignment(QtCore.Qt.AlignCenter)
        self.TODSPP.setObjectName("TODSPP")
        self.verticalLayout_3.addWidget(self.TODSPP)
        self.FODSPP = QtWidgets.QLineEdit(self.centralwidget)
        self.FODSPP.setFocusPolicy(QtCore.Qt.NoFocus)
        self.FODSPP.setAlignment(QtCore.Qt.AlignCenter)
        self.FODSPP.setObjectName("FODSPP")
        self.verticalLayout_3.addWidget(self.FODSPP)
        self.QODSPP = QtWidgets.QLineEdit(self.centralwidget)
        self.QODSPP.setFocusPolicy(QtCore.Qt.NoFocus)
        self.QODSPP.setAlignment(QtCore.Qt.AlignCenter)
        self.QODSPP.setObjectName("QODSPP")
        self.verticalLayout_3.addWidget(self.QODSPP)
        self.messageBox = QtWidgets.QTextEdit(self.centralwidget)
        self.messageBox.setFocusPolicy(QtCore.Qt.NoFocus)
        self.messageBox.setReadOnly(True)
        self.messageBox.setObjectName("messageBox")
        self.verticalLayout_3.addWidget(self.messageBox)
        self.gridLayout.addLayout(self.verticalLayout_3, 1, 3, 2, 1)
        self.verticalLayout_4 = QtWidgets.QVBoxLayout()
        self.verticalLayout_4.setObjectName("verticalLayout_4")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout()
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.delayLine = QtWidgets.QLineEdit(self.centralwidget)
        self.delayLine.setMinimumSize(QtCore.QSize(0, 30))
        self.delayLine.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.delayLine.setAlignment(QtCore.Qt.AlignCenter)
        self.delayLine.setObjectName("delayLine")
        self.horizontalLayout.addWidget(self.delayLine)
        self.verticalLayout_2.addLayout(self.horizontalLayout)
        self.SPP1 = QtWidgets.QLineEdit(self.centralwidget)
        self.SPP1.setMinimumSize(QtCore.QSize(0, 30))
        self.SPP1.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.SPP1.setText("")
        self.SPP1.setAlignment(QtCore.Qt.AlignCenter)
        self.SPP1.setObjectName("SPP1")
        self.SPP1.setStyleSheet("""QLineEdit { color: blue }""")
        self.verticalLayout_2.addWidget(self.SPP1)
        self.SPP2 = QtWidgets.QLineEdit(self.centralwidget)
        self.SPP2.setMinimumSize(QtCore.QSize(0, 30))
        self.SPP2.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.SPP2.setAlignment(QtCore.Qt.AlignCenter)
        self.SPP2.setObjectName("SPP2")
        self.verticalLayout_2.addWidget(self.SPP2)
        self.SPP3 = QtWidgets.QLineEdit(self.centralwidget)
        self.SPP3.setMinimumSize(QtCore.QSize(0, 30))
        self.SPP3.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.SPP3.setAlignment(QtCore.Qt.AlignCenter)
        self.SPP3.setObjectName("SPP3")
        self.verticalLayout_2.addWidget(self.SPP3)
        self.SPP4 = QtWidgets.QLineEdit(self.centralwidget)
        self.SPP4.setMinimumSize(QtCore.QSize(0, 30))
        self.SPP4.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.SPP4.setAlignment(QtCore.Qt.AlignCenter)
        self.SPP4.setObjectName("SPP4")
        self.SPP2.setStyleSheet("""QLineEdit { color: orange }""")
        self.SPP3.setStyleSheet("""QLineEdit { color: green }""")
        self.SPP4.setStyleSheet("""QLineEdit { color: purple }""")
        self.verticalLayout_2.addWidget(self.SPP4)
        self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_4.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.pushButton_4.setObjectName("pushButton_4")
        self.verticalLayout_2.addWidget(self.pushButton_4)
        self.verticalLayout_4.addLayout(self.verticalLayout_2)
        self.gridLayout.addLayout(self.verticalLayout_4, 1, 1, 2, 1)
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.loadButton = QtWidgets.QPushButton(self.centralwidget)
        self.loadButton.setMinimumSize(QtCore.QSize(150, 30))
        self.loadButton.setObjectName("loadButton")
        self.horizontalLayout_2.addWidget(self.loadButton)
        self.pushButton_7 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_7.setObjectName("pushButton_7")
        self.horizontalLayout_2.addWidget(self.pushButton_7)
        self.verticalLayout.addLayout(self.horizontalLayout_2)
        self.treeWidget = QtWidgets.QTreeWidget(self.centralwidget)
        self.treeWidget.setExpandsOnDoubleClick(False)
        self.treeWidget.setObjectName("treeWidget")
        font = QtGui.QFont()
        font.setBold(False)
        font.setUnderline(True)
        font.setWeight(50)
        font.setStrikeOut(False)
        self.treeWidget.headerItem().setFont(0, font)
        self.verticalLayout.addWidget(self.treeWidget)
        self.gridLayout.addLayout(self.verticalLayout, 0, 0, 3, 1)
        self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_4.setObjectName("horizontalLayout_4")
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.pushButton_2.setObjectName("pushButton_2")
        self.horizontalLayout_4.addWidget(self.pushButton_2)
        self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_3.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.pushButton_3.setObjectName("pushButton_3")
        self.horizontalLayout_4.addWidget(self.pushButton_3)
        self.gridLayout.addLayout(self.horizontalLayout_4, 1, 2, 1, 1)
        self.widget = PlotWidget(self.centralwidget)
        self.widget.setMinimumSize(QtCore.QSize(500, 450))
        self.widget.setObjectName("widget")
        self.gridLayout.addWidget(self.widget, 0, 1, 1, 3)
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        self.verticalLayout_8 = QtWidgets.QVBoxLayout()
        self.verticalLayout_8.setObjectName("verticalLayout_8")
        self.spp_ref = QtWidgets.QLineEdit(self.centralwidget)
        self.spp_ref.setMinimumSize(QtCore.QSize(0, 30))
        self.spp_ref.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.spp_ref.setAlignment(QtCore.Qt.AlignCenter)
        self.spp_ref.setObjectName("spp_ref")
        self.verticalLayout_8.addWidget(self.spp_ref)
        self.fitOrderLine = QtWidgets.QLineEdit(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.fitOrderLine.sizePolicy().hasHeightForWidth())
        self.fitOrderLine.setSizePolicy(sizePolicy)
        self.fitOrderLine.setMinimumSize(QtCore.QSize(0, 30))
        self.fitOrderLine.setMaximumSize(QtCore.QSize(651651, 16777215))
        self.fitOrderLine.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.fitOrderLine.setAlignment(QtCore.Qt.AlignCenter)
        self.fitOrderLine.setObjectName("fitOrderLine")
        self.verticalLayout_8.addWidget(self.fitOrderLine)
        self.pushButton_5 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_5.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.pushButton_5.setObjectName("pushButton_5")
        self.verticalLayout_8.addWidget(self.pushButton_5)
        self.horizontalLayout_3.addLayout(self.verticalLayout_8)
        self.pushButton_6 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_6.setMinimumSize(QtCore.QSize(120, 30))
        self.pushButton_6.setObjectName("pushButton_6")
        self.horizontalLayout_3.addWidget(self.pushButton_6)
        self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_6.setObjectName("horizontalLayout_6")
        self.horizontalLayout_3.addLayout(self.horizontalLayout_6)
        self.gridLayout.addLayout(self.horizontalLayout_3, 2, 2, 1, 1)
        SPP.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(SPP)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1302, 26))
        self.menubar.setObjectName("menubar")
        SPP.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(SPP)
        self.statusbar.setObjectName("statusbar")
        SPP.setStatusBar(self.statusbar)

        self.retranslateUi(SPP)
        QtCore.QMetaObject.connectSlotsByName(SPP)

    def retranslateUi(self, SPP):
        _translate = QtCore.QCoreApplication.translate
        SPP.setWindowTitle(_translate("SPP", "SPP Panel"))
        self.GDSPP.setPlaceholderText(_translate("SPP", "GD "))
        self.GDDSPP.setPlaceholderText(_translate("SPP", "GDD"))
        self.TODSPP.setPlaceholderText(_translate("SPP", "TOD"))
        self.FODSPP.setPlaceholderText(_translate("SPP", "FOD"))
        self.QODSPP.setPlaceholderText(_translate("SPP", "QOD"))
        __sortingEnabled = self.treeWidget.isSortingEnabled()
        self.treeWidget.setSortingEnabled(False)
        self.treeWidget.setSortingEnabled(__sortingEnabled)
        self.delayLine.setPlaceholderText(_translate("SPP", "delay (fs)"))
        self.SPP1.setPlaceholderText(_translate("SPP", "SPP1"))
        self.SPP2.setPlaceholderText(_translate("SPP", "SPP2"))
        self.SPP3.setPlaceholderText(_translate("SPP", "SPP3"))
        self.SPP4.setPlaceholderText(_translate("SPP", "SPP4"))
        self.pushButton_4.setText(_translate("SPP", "Set SPP\'s"))
        self.loadButton.setText(_translate("SPP", "Load item"))
        self.pushButton_7.setText(_translate("SPP", "Delete item"))
        self.treeWidget.headerItem().setText(0, _translate("SPP", "Loaded files"))
        self.pushButton_2.setText(_translate("SPP", "Clickable SPP"))
        self.pushButton_3.setText(_translate("SPP", "Stop clicks"))
        self.spp_ref.setPlaceholderText(_translate("SPP", "Reference point"))
        self.fitOrderLine.setPlaceholderText(_translate("SPP", "Fit order "))
        self.pushButton_5.setText(_translate("SPP", "Reset all"))
        self.pushButton_6.setText(_translate("SPP", "Fit and report"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    SPP = QtWidgets.QMainWindow()
    ui = Ui_SPP()
    ui.setupUi(SPP)
    SPP.show()
    sys.exit(app.exec_())

plotwidget.py

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5.QtWidgets import QWidget, QVBoxLayout

class PlotWidget(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.canvas = FigureCanvas(Figure())
        vertical_layout = QVBoxLayout()
        vertical_layout.addWidget(self.canvas)
        self.canvas.axes = self.canvas.figure.add_subplot(111)
        self.setLayout(vertical_layout)

My question mainly aims at finding a better data structure, but every little improvement is appreciated. Note that I intentionally didn't follow PEP8 there, because PyQt's naming convention is against that, and I'd rather keep the code coherent. Basically you can load any x-y dataset from a txt file which looks like this:

1,1
2,4
3,9
4,16
..

Here is an example dataset you can load.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I do not have worked a lot with PyQt, but in most GUI libraries tree-view-items have facilities to store userdata. For QTreeWidgetItem you can use the data and setData methods. It might conflict with the MVC-pattern but practicality beats purity.

My revised logic.py with self.xData and self.yData removed and data stored in the GUI objects:

import numpy as np
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QAbstractItemView, QFileDialog, QTreeWidgetItem

from ui import Ui_SPP

class SPPWindow(QtWidgets.QMainWindow, Ui_SPP):
    def __init__(self, parent=None):
        super(SPPWindow, self).__init__(parent)
        self.setupUi(self)
        self.treeWidget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.loadButton.clicked.connect(self.loadData)
        self.treeWidget.itemSelectionChanged.connect(self.previewData)

    def previewData(self):
        """ Function to update the plot."""
        curr = self.treeWidget.currentItem()
        if curr:
            stuff = curr.data(0, QTreeWidgetItem.UserType)
            print(stuff['name'])
            print(stuff['meta'])
            self.widget.canvas.axes.clear()
            self.widget.canvas.axes.plot(stuff['Xdata'], stuff['Ydata'], 'r')
            self.widget.canvas.axes.grid()
            self.widget.canvas.draw()


    def loadData(self):
        """ Function to load file into QTreeWidget"""
        options = QFileDialog.Options()
        fileName, _ = QFileDialog.getOpenFileName(None,"Load interferogram", "","All Files (*);;Text Files (*.txt)", options=options)
        try:
            if fileName:
                x, y = np.loadtxt(fileName, unpack=True, delimiter=',', usecols=(0, 1))
                name = fileName.split('/')[-1]
                l1 = QTreeWidgetItem([name])
                stuff = {
                    'Xdata'  : x,
                    'Ydata'  : y,
                    'name'   : name,
                    'meta'   : 'other data',
                }
                l1.setData(0, QTreeWidgetItem.UserType, stuff)
                self.treeWidget.addTopLevelItem(l1)
            self.previewData()
        except Exception as e:
            print(e)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    application = SPPWindow()
    application.show()
    sys.exit(app.exec_())
\$\endgroup\$
2
  • \$\begingroup\$ This seems interesting. Do you know how well it behaves when you load a bigger amount of data? (Approx. 50-60 QTreeWidgetItem) \$\endgroup\$ Dec 19, 2019 at 20:19
  • \$\begingroup\$ @Péter Leéh No I don't, but I don't expect problems, widely used (gui) libraries tend to be good. \$\endgroup\$
    – Jan Kuiken
    Dec 19, 2019 at 20:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.