Python | PyQtGraphにテキスト/ボタン/コンボボックス/テキストボックス/チェックボックス/線/ラジオボタンを実装する方法

PyQtGraph

Python PyQtGraphにテキスト/ボタン/コンボボックス/テキストボックス/チェックボックス/線/ラジオボタンを実装する方法を説明する。QLabel/QPushButton/QComboBox/QLineEdit/QCheckBox/QFrame/QRadioButtonを使用する。

完成イメージ

骨子

QLabel/QPushButton/QComboBox/QLineEdit/QCheckBox/QFrame/QRadioButtonのクラスを準備しグラフ内で各クラスのインスタンスを使う。

#!/usr/bin/env python3

from PyQt6.QtWidgets import (QApplication, QWidget, QGraphicsProxyWidget,
                            QLabel, QPushButton, QComboBox, QLineEdit,
                            QCheckBox, QFrame, QRadioButton, QButtonGroup)
from PyQt6 import QtGui, QtCore
import pyqtgraph as pg
import sys


class GuiWindow(QWidget):
    # 中略
    self.graph = pg.GraphicsLayoutWidget(show=True)
    ここでQLabel/QPushButton/QComboBox/QLineEdit
    /QCheckBox/QFrame/QRadioButtonのクラスのインスタンスを使う


class QLabelのクラス(QWidget):
    ここにQlabelのオブジェクトを記述


class QPushButtonのクラス(QWidget):
    ここにQPushButtonのオブジェクトを記述


class QComboBoxのクラス(QWidget):
    ここにQComboBoxのオブジェクトを記述


class QLineEditのクラス(QWidget):
    ここにQLineEditのオブジェクトを記述


class QCheckBoxのクラス(QWidget):
    ここにQCheckBoxのオブジェクトを記述


class QFrameのクラス(QWidget):
    ここにQFrameのオブジェクトを記述


class QRadioButtonのクラス(QWidget):
    ここにQRadioButtonのオブジェクトを記述


if __name__ == '__main__':
    qapp = QApplication(sys.argv)
    window = GuiWindow()
    sys.exit(qapp.exec())

具体例

#!/usr/bin/env python3

from PyQt6.QtWidgets import (QApplication, QWidget, QGraphicsProxyWidget,
                            QLabel, QPushButton, QComboBox, QLineEdit,
                            QCheckBox, QFrame, QRadioButton, QButtonGroup)
from PyQt6 import QtGui, QtCore
import pyqtgraph as pg
import sys
import numpy as np


class GuiWindow(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.set_variables()
        self.set_objects()
        self.set_data()
        self.plot_data()
        self.set_appearance()

    def set_variables(self):
        self.range_x_min = -1
        self.range_x_max = 5
        self.pen_color = (255,255,0)
        self.pen_width = 10
        self.pen = pg.mkPen(self.pen_color, width=self.pen_width)

    def set_data(self):
        self.freq = 0.2  # サイン波の周波数 [Hz]
        self.Ts = 0.1  # サンプリング周期 [秒]
        self.Ns = 1000  # サンプリング点数 [個]
        self.ns = np.arange(0, self.Ns)  # サンプル番号

        self.x = self.ns * self.Ts  # 時間データ
        self.y = np.sin(2 * np.pi * self.freq * (self.ns * self.Ts))  # サイン波

    def clear_data(self):
        self.p.clear()

    def plot_data(self):
        self.p.plot(x=self.x, y=self.y, pen=self.pen)

    def set_appearance(self):
        self.p.setLabel('left', '大きさ')
        self.p.setLabel('bottom', '時間 [秒]')
        self.p.setXRange(self.range_x_min, self.range_x_max)

    def set_objects(self):
        self.graph = pg.GraphicsLayoutWidget(show=True)

        # row0-7
        self.p = self.graph.addPlot(row=0, col=0, rowspan=9, colspan=4)

        # row0-3
        self.multiSelector1 = MultiSelector(app=self, win=self.graph, row=0, col=4, w=100, h=25, text='極太')
        self.multiSelector2 = MultiSelector(app=self, win=self.graph, row=1, col=4, w=100, h=25, text='太め')
        self.multiSelector3 = MultiSelector(app=self, win=self.graph, row=2, col=4, w=100, h=25, text='普通')
        self.multiSelector4 = MultiSelector(app=self, win=self.graph, row=3, col=4, w=100, h=25, text='細め')
        self.group = QButtonGroup()
        self.group.addButton(self.multiSelector4.object)
        self.group.addButton(self.multiSelector3.object)
        self.group.addButton(self.multiSelector1.object)
        self.group.addButton(self.multiSelector2.object)

        # row4
        self.lineSeparator1 = LineSeparator(app=self, win=self.graph, row=4, col=4, w=100, h=1)

        # row6
        self.lineSeparator2 = LineSeparator(app=self, win=self.graph, row=6, col=4, w=100, h=1)

        # row7
        self.touchPadPanel1 = TouchPadPanel(app=self, win=self.graph, row=7, col=4, w=100, h=25, text='CSV化')

        # row5(row6,7より前面に出したいので後に作成)
        self.pullDownPanel1 = PullDownPanel(app=self, win=self.graph, row=5, col=4, w=100, h=25)
        self.pullDownPanel1.object.addItem(" 黃")
        self.pullDownPanel1.object.addItem(" 青")
        self.pullDownPanel1.object.addItem(" 赤")

        # row8
        self.flexibleSpace1 = FlexibleSpace(app=self, win=self.graph, row=8, col=4, w=100)

        # row9
        self.letterDisplay1 = LetterDisplay(app=self, win=self.graph, row=9, col=0, w=50, h=25, text='x max')
        self.textEditFrame1 = TextEditFrame(app=self, win=self.graph, row=9, col=1, w=100, h=25, name='x_max', text=str(self.range_x_max))
        self.checkReceiver1 = CheckReceiver(app=self, win=self.graph, row=9, col=2, w=100, h=25, text='固定', name='x_max固定')
        self.flexibleSpace2 = FlexibleSpace(app=self, win=self.graph, row=9, col=3, h=25)

        # row10
        self.letterDisplay2 = LetterDisplay(app=self, win=self.graph, row=10, col=0, w=50, h=25, text='x min')
        self.textEditFrame2 = TextEditFrame(app=self, win=self.graph, row=10, col=1, w=100, h=25, name='x_min', text=str(self.range_x_min))
        self.checkReceiver2 = CheckReceiver(app=self, win=self.graph, row=10, col=2, w=100, h=25, text='固定', name='x_min固定')
        self.flexibleSpace3 = FlexibleSpace(app=self, win=self.graph, row=10, col=3, h=25)


class FlexibleSpace(QWidget):
    def __init__(self, app, win, row, col, rowspan=1, colspan=1, w=0, h=0, name='', text=''):
        super().__init__()
        self.set_layout(win=win, row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.set_object(app=app, w=w, h=h, name=name, text=text)
        self.set_proxy()
        self.setInnerText(text=text)

    def set_layout(self, win, row, col, rowspan, colspan):
        self.p = win.addLayout(row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.p.setContentsMargins(10,0,10,0)  # 左,上,右,下

    def set_object(self, app, w, h, name, text):
        def make_object():
            self.object = QLabel()

        def set_style():
            style = ('QLabel{'
                    'background-color: rgba(0,0,0,0);'
                    'color: darkgray;'
                    '}')
            self.object.setStyleSheet(style)

        def set_size(w, h):
            def set_wsize(w):
                self.object.setMinimumWidth(w)
                self.object.setMaximumWidth(w)

            def set_hsize(h):
                self.object.setMinimumHeight(h)
                self.object.setMaximumHeight(h)

            if w > 0:
                set_wsize(w)
            else:
                pass  # free size
            if h > 0:
                set_hsize(h)
            else:
                pass  # free size

        make_object()
        set_style()
        set_size(w=w, h=h)

    def set_proxy(self):
        self.proxy = QGraphicsProxyWidget()
        self.item = self.p.addItem(self.proxy)
        self.proxy.setWidget(self.object)

    def setInnerText(self, text):
        self.object.setText(text)


class LetterDisplay(QWidget):
    def __init__(self, app, win, row, col, rowspan=1, colspan=1, w=0, h=0, name='', text=''):
        super().__init__()
        self.set_layout(win=win, row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.set_object(app=app, w=w, h=h, name=name, text=text)
        self.set_proxy()
        self.setInnerText(text=text)

    def set_layout(self, win, row, col, rowspan, colspan):
        self.p = win.addLayout(row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.p.setContentsMargins(10,0,10,0)  # 左,上,右,下

    def set_object(self, app, w, h, name, text):
        def make_object():
            self.object = QPushButton()

        def set_style():
            style = ('QPushButton{'
                    'background-color: rgba(0,0,0,0);'
                    'color: darkgray;'
                    'text-align: left;'
                    '}')
            self.object.setStyleSheet(style)

        def set_size(w, h):
            def set_wsize(w):
                self.object.setMinimumWidth(w)
                self.object.setMaximumWidth(w)

            def set_hsize(h):
                self.object.setMinimumHeight(h)
                self.object.setMaximumHeight(h)

            if w > 0:
                set_wsize(w)
            else:
                pass  # free size
            if h > 0:
                set_hsize(h)
            else:
                pass  # free size

        def connect_signal_slot():
            self.object.clicked.connect(clicked)

        def clicked():
            print('clicked')

        make_object()
        set_style()
        set_size(w=w, h=h)
        connect_signal_slot()

    def set_proxy(self):
        self.proxy = QGraphicsProxyWidget()
        self.item = self.p.addItem(self.proxy)
        self.proxy.setWidget(self.object)

    def setInnerText(self, text):
        self.object.setText(text)


class PullDownPanel(QWidget):
    def __init__(self, app, win, row, col, rowspan=1, colspan=1, w=0, h=0, name=''):
        super().__init__()
        self.set_layout(win=win, row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.set_object(app=app, w=w, h=h, name=name)
        self.set_proxy()

    def set_layout(self, win, row, col, rowspan, colspan):
        self.p = win.addLayout(row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.p.setContentsMargins(10,0,10,0)  # 左,上,右,下

    def set_object(self, app, w, h, name):
        def make_object():
            self.object = QComboBox()

        def set_style():
            style = ('QComboBox{'
                    'background-color: rgb(77,77,77);'
                    'color: rgb(177,177,177);'
                    '}')
            self.object.setStyleSheet(style)

        def set_size(w, h):
            def set_wsize(w):
                self.object.setMinimumWidth(w)
                self.object.setMaximumWidth(w)

            def set_hsize(h):
                self.object.setMinimumHeight(h)
                self.object.setMaximumHeight(h)

            if w > 0:
                set_wsize(w)
            else:
                pass  # free size
            if h > 0:
                set_hsize(h)
            else:
                pass  # free size

        def connect_signal_slot():
            self.object.textActivated[str].connect(onActivated)

        def onActivated(text):
            if text == ' 黃':
                app.pen_color = (255,255,0)
            if text == ' 青':
                app.pen_color = (0,0,255)
            if text == ' 赤':
                app.pen_color = (255,0,0)
            app.pen = pg.mkPen(app.pen_color, width=app.pen_width)
            app.clear_data()
            app.plot_data()

        make_object()
        set_style()
        set_size(w=w, h=h)
        connect_signal_slot()

    def set_proxy(self):
        self.proxy = QGraphicsProxyWidget()
        self.item = self.p.addItem(self.proxy)
        self.proxy.setWidget(self.object)


class TextEditFrame(QWidget):
    def __init__(self, app, win, row, col, rowspan=1, colspan=1, w=0, h=0, name='', text=''):
        super().__init__()
        self.set_layout(win=win, row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.set_object(app=app, w=w, h=h, name=name, text=text)
        self.set_proxy()
        self.setInnerText(text=text)

    def set_layout(self, win, row, col, rowspan, colspan):
        self.p = win.addLayout(row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.p.setContentsMargins(10,0,10,0)  # 左,上,右,下

    def set_object(self, app, w, h, name, text):
        def make_object():
            self.object = QLineEdit()

        def set_style():
            style = ('QLineEdit{'
                    'background-color: rgb(77,77,77);'
                    'color: white;'
                    '}')
            self.object.setStyleSheet(style)
            self.object.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)

        def set_size(w, h):
            def set_wsize(w):
                self.object.setMinimumWidth(w)
                self.object.setMaximumWidth(w)

            def set_hsize(h):
                self.object.setMinimumHeight(h)
                self.object.setMaximumHeight(h)

            if w > 0:
                set_wsize(w)
            else:
                pass  # free size
            if h > 0:
                set_hsize(h)
            else:
                pass  # free size

        def connect_signal_slot():
            self.object.textChanged[str].connect(onChanged)

        def onChanged(text):
            try:
                if name == 'x_max':
                    app.range_x_max = int(text)
                    app.p.setXRange(app.range_x_min, app.range_x_max)
                elif name == 'x_min':
                    app.range_x_min = int(text)
                    app.p.setXRange(app.range_x_min, app.range_x_max)
            except:
                pass

        make_object()
        set_style()
        set_size(w=w, h=h)
        connect_signal_slot()

    def set_proxy(self):
        self.proxy = QGraphicsProxyWidget()
        self.item = self.p.addItem(self.proxy)
        self.proxy.setWidget(self.object)

    def setInnerText(self, text):
        self.object.setText(text)


class CheckReceiver(QWidget):
    def __init__(self, app, win, row, col, rowspan=1, colspan=1, w=0, h=0, name='', text=''):
        super().__init__()
        self.set_layout(win=win, row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.set_object(app=app, w=w, h=h, name=name, text=text)
        self.set_proxy()
        self.setInnerText(text=text)

    def set_layout(self, win, row, col, rowspan, colspan):
        self.p = win.addLayout(row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.p.setContentsMargins(10,0,10,0)  # 左,上,右,下

    def set_object(self, app, w, h, name, text):
        def make_object():
            self.object = QCheckBox(text)

        def set_style():
            style = ('QCheckBox{'
                    'background-color: rgba(0,0,0,0);'
                    'color: darkgray;'
                    'text-align: left;'
                    '}')
            self.object.setStyleSheet(style)

        def set_size(w, h):
            def set_wsize(w):
                self.object.setMinimumWidth(w)
                self.object.setMaximumWidth(w)

            def set_hsize(h):
                self.object.setMinimumHeight(h)
                self.object.setMaximumHeight(h)

            if w > 0:
                set_wsize(w)
            else:
                pass  # free size
            if h > 0:
                set_hsize(h)
            else:
                pass  # free size

        def connect_signal_slot():
            self.object.stateChanged.connect(onChanged)

        def onChanged():
            if self.object.isChecked():
                if name == 'x_max固定':
                    app.textEditFrame1.object.setDisabled(True)
                elif name == 'x_min固定':
                    app.textEditFrame2.object.setDisabled(True)
            else:
                if name == 'x_max固定':
                    app.textEditFrame1.object.setDisabled(False)
                elif name == 'x_min固定':
                    app.textEditFrame2.object.setDisabled(False)

        make_object()
        set_style()
        set_size(w=w, h=h)
        connect_signal_slot()

    def set_proxy(self):
        self.proxy = QGraphicsProxyWidget()
        self.item = self.p.addItem(self.proxy)
        self.proxy.setWidget(self.object)

    def setInnerText(self, text):
        self.object.setText(text)


class LineSeparator(QWidget):
    def __init__(self, app, win, row, col, rowspan=1, colspan=1, w=0, h=0, name=''):
        super().__init__()
        self.set_layout(win=win, row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.set_object(app=app, w=w, h=h, name=name)
        self.set_proxy()

    def set_layout(self, win, row, col, rowspan, colspan):
        self.p = win.addLayout(row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.p.setContentsMargins(10,0,10,0)  # 左,上,右,下

    def set_object(self, app, w, h, name):
        def make_object():
            self.object = QFrame()

        def set_style():
            style = ('QFrame{'
                    'color: rgb(77,77,77);'
                    'background-color: rgb(77,77,77);'
                    '}')
            self.object.setStyleSheet(style)
            self.object.setFrameStyle(QFrame.Shape.Panel | QFrame.Shadow.Plain)

        def set_size(w, h):
            def set_wsize(w):
                self.object.setMinimumWidth(w)
                self.object.setMaximumWidth(w)

            def set_hsize(h):
                self.object.setMinimumHeight(h)
                self.object.setMaximumHeight(h)

            if w > 0:
                set_wsize(w)
            else:
                pass  # free size
            if h > 0:
                set_hsize(h)
            else:
                pass  # free size

        make_object()
        set_style()
        set_size(w=w, h=h)

    def set_proxy(self):
        self.proxy = QGraphicsProxyWidget()
        self.item = self.p.addItem(self.proxy)
        self.proxy.setWidget(self.object)


class TouchPadPanel(QWidget):
    def __init__(self, app, win, row, col, rowspan=1, colspan=1, w=0, h=0, name='', text=''):
        super().__init__()
        self.set_layout(win=win, row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.set_object(app=app, w=w, h=h, name=name, text=text)
        self.set_proxy()
        self.setInnerText(text=text)

    def set_layout(self, win, row, col, rowspan, colspan):
        self.p = win.addLayout(row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.p.setContentsMargins(10,0,10,0)  # 左,上,右,下

    def set_object(self, app, w, h, name, text):
        def make_object():
            self.object = QPushButton()

        def set_style():
            style = ('QPushButton{'
                    'background-color: rgb(77,77,77);'
                    'color: rgb(177,177,177);'
                    'border: none;'
                    '}'
                    'QPushButton::hover {'
                    'background-color: gray;'
                    'color: black;'
                    '}')
            self.object.setStyleSheet(style)

        def set_size(w, h):
            def set_wsize(w):
                self.object.setMinimumWidth(w)
                self.object.setMaximumWidth(w)

            def set_hsize(h):
                self.object.setMinimumHeight(h)
                self.object.setMaximumHeight(h)

            if w > 0:
                set_wsize(w)
            else:
                pass  # free size
            if h > 0:
                set_hsize(h)
            else:
                pass  # free size

        def connect_signal_slot():
            self.object.clicked.connect(clicked)

        def clicked(text):
            output = np.stack([app.x, app.y]).transpose(1, 0)
            np.savetxt('output.csv', output, delimiter=',')

        def set_event():
            self.app = app
            self.object.installEventFilter(self)

        make_object()
        set_style()
        set_size(w=w, h=h)
        connect_signal_slot()
        set_event()

    def set_proxy(self):
        self.proxy = QGraphicsProxyWidget()
        self.item = self.p.addItem(self.proxy)
        self.proxy.setWidget(self.object)

    def setInnerText(self, text):
        self.object.setText(text)

    def eventFilter(self, object, event):
        if event.type() == QtCore.QEvent.Type.Enter:
            self.app.flexibleSpace1.setInnerText('CSV化する?')
        elif event.type() == QtCore.QEvent.Type.Leave:
            self.app.flexibleSpace1.setInnerText('')
        return False


class MultiSelector(QWidget):
    def __init__(self, app, win, row, col, rowspan=1, colspan=1, w=0, h=0, name='', text=''):
        super().__init__()
        self.set_layout(win=win, row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.set_object(app=app, w=w, h=h, name=name, text=text)
        self.set_proxy()
        self.setInnerText(text=text)

    def set_layout(self, win, row, col, rowspan, colspan):
        self.p = win.addLayout(row=row, col=col, rowspan=rowspan, colspan=colspan)
        self.p.setContentsMargins(10,0,10,0)  # 左,上,右,下

    def set_object(self, app, w, h, name, text):
        def make_object():
            self.object = QRadioButton()

        def set_style():
            style = ('QRadioButton{'
                    'background-color: rgba(0,0,0,0);'
                    'color: darkgray;'
                    '}')
            self.object.setStyleSheet(style)
            self.object.setChecked(True)

        def set_size(w, h):
            def set_wsize(w):
                self.object.setMinimumWidth(w)
                self.object.setMaximumWidth(w)

            def set_hsize(h):
                self.object.setMinimumHeight(h)
                self.object.setMaximumHeight(h)

            if w > 0:
                set_wsize(w)
            else:
                pass  # free size
            if h > 0:
                set_hsize(h)
            else:
                pass  # free size

        def connect_signal_slot():
            self.object.toggled.connect(toggled)

        def toggled():
            try:
                radioBtn = app.graph.sender()
                if radioBtn.isChecked():
                    if radioBtn.text() == '極太':
                        app.pen_width = 100
                    if radioBtn.text() == '太め':
                        app.pen_width = 10
                    if radioBtn.text() == '普通':
                        app.pen_width = 5
                    if radioBtn.text() == '細め':
                        app.pen_width = 1
                app.pen = pg.mkPen(app.pen_color, width=app.pen_width)
                app.clear_data()
                app.plot_data()
            except:
                pass

        make_object()
        set_style()
        set_size(w=w, h=h)
        connect_signal_slot()

    def set_proxy(self):
        self.proxy = QGraphicsProxyWidget()
        self.item = self.p.addItem(self.proxy)
        self.proxy.setWidget(self.object)

    def setInnerText(self, text):
        self.object.setText(text)


if __name__ == '__main__':
    qapp = QApplication(sys.argv)
    window = GuiWindow()
    sys.exit(qapp.exec())

まとめ

Python PyQtGraphにテキスト/ボタン/コンボボックス/テキストボックス/チェックボックス/線/ラジオボタンを実装する方法を説明した。

コメント