使用自定义FigureCanvas小部件进行Pyside绘图

时间:2019-05-20 23:11:38

标签: python user-interface matplotlib pyside wizard

首先,我是Pyside的初学者,我欢迎建设性的反馈。我也感谢任何看过此内容并尝试运行我的代码的人。预先谢谢你!

我还应该提到我在代码中使用https://github.com/mottosso/Qt.py作为Qt导入。

我正在制作一个GUI以执行一些硬件单元测试。该GUI带有嵌入式FigureCanvas图,用于在用户单击按钮时显示从硬件捕获的数据。我将unittest.TestCase子类化以插入PlotCanvas(具有一些简单方法的自定义FigureCanvas类)。然后,我创建一个包含多个页面的向导,每个页面都有一个独立的图。

现在,如果我不对unittest.TestCase进行子类化,只需创建一个静态plot_canvas,这将起作用。在我的主要代码块中看到DEBUG_STATE = 1。

# Uses statically defined plot canvas, no subclassing
class TestPage1(unittest.TestCase):

    PLOT_COLS = 1
    PLOT_SIZE = (12, 6)
    plot_canvas = PlotCanvas(size=PLOT_SIZE, cols=PLOT_COLS)

    def test_01(self):
        x = np.linspace(0,10,50)
        self.plot_canvas.plot(x, 2*x)

但是,如果我使用StaticPlottingTestCase,代码几乎相同,则无法插入多个页面。画布没有显示在首页上。我认为这与QWizard有关,但我不确定。参见DEBUG_STATE = 3。

class StaticPlottingTestCase(unittest.TestCase):
    # Static definition
    PLOT_COLS = 1
    PLOT_SIZE = (12, 6)
    plot_canvas = PlotCanvas(size=PLOT_SIZE, cols=PLOT_COLS)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

为解决此问题,我制作了另一个类InstancePlottingTestCase,该类已将plot_canvas定义为类变量。现在,情节出现在两个页面中,但是情节不起作用。参见DEBUG_STATE = 4。

class InstancePlottingTestCase(unittest.TestCase):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Instance definition
        self.plot_canvas = PlotCanvas()

我已经尝试了数小时的研究,尽管代码不是那么干净,但到目前为止,我几乎只使用我的原始方法。我已经广泛研究了Pyside文档。

这是我的最小工作示例。可以将我的主块中的DEBUG_STATE变量设置为不同的值,以模拟我遇到的问题。

DEBUG_STATE = 1:我的基准线方法。这可以按预期工作。

DEBUG_STATE = 2:StaticPlottingTestCase的单个页面。这行得通。

DEBUG_STATE = 3:两页的StaticPlottingTestCase。第一页没有图。第一页上的绘图似乎会影响第二页。

DEBUG_STATE = 4:两页的InstancePlottingTestCase。显示了情节,但无法绘制。

更新:我发现,如果我添加了两个页面,它们的名称相同(DEBUG_STATE = 6),则结果与DEBUG_STATE = 3是相同的。

完整代码:

import io
import sys
import unittest
from unittest.runner import TextTestResult

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_qt5agg import \
    FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

from Qt import QtCore, QtGui, QtWidgets
from Qt.QtCore import QCoreApplication
from Qt.QtWidgets import (QFormLayout, QPushButton, QVBoxLayout, QWizard,
                          QWizardPage)


def discover_tests(test_case):
    """Compile a list of tests for the given test case.

    Parameters
    ----------
    test_case : TestCase

    Returns
    -------
    names : list
        Test names

    """
    return [a for a in dir(test_case) if a.startswith('test_')]


class PlotCanvas(FigureCanvas):

    def __init__(self, figsize=(8,6), *args, **kwargs):
        # Initialize the figure first
        self.fig = Figure(figsize=figsize)
        self.fig.set_facecolor('white')

        # Create the canvas with the created figure
        super(PlotCanvas, self).__init__(self.fig)

        # Add axes to the figure
        self.fig.add_subplot(111)
        self.ax = self.fig.get_axes()[0]

    def plot(self, x, y, *args, **kwargs):
        self.ax.clear()
        self.ax.plot(x, y)
        self.draw()


class StaticPlottingTestCase(unittest.TestCase):
    # Static definition
    PLOT_COLS = 1
    PLOT_SIZE = (12, 6)
    plot_canvas = PlotCanvas(size=PLOT_SIZE, cols=PLOT_COLS)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class InstancePlottingTestCase(unittest.TestCase):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Instance definition
        self.plot_canvas = PlotCanvas()


class TestPage(QWizardPage):

    def __init__(self, unittest_class, static_canvas=True, *args, **kwargs):
        super(TestPage, self).__init__(*args, **kwargs)

        # Assign the unit test class
        self.unittest_class = unittest_class

        # Define overall layout as an hbox
        top_hbox = QtWidgets.QHBoxLayout()

        # Populate left side with unittest buttons
        unittest_vbox = QVBoxLayout()
        formLayout = QFormLayout()
        self.buttons = {}
        unittest_names = discover_tests(unittest_class)
        for name in unittest_names:
            button = QPushButton('Push to Start', self)
            button.setCheckable(True)
            button.unittest_name = name
            button.setMinimumWidth(100)
            button.clicked.connect(self.onButton)
            self.buttons[name] = button
            formLayout.addRow(name + ':', button)
        unittest_vbox.addLayout(formLayout)

        # Central plot
        plot_vbox = QVBoxLayout()

        # Static plot
        if static_canvas:
            self.plot_canvas = self.unittest_class.plot_canvas
        else:
            self.plot_canvas = self.unittest_class().plot_canvas

        plot_vbox.addWidget(self.plot_canvas)

        # Set overall layout
        top_hbox.addLayout(unittest_vbox)
        top_hbox.addLayout(plot_vbox)
        self.setLayout(top_hbox)

    def onButton(self):
        """ Create a TestSuite with just the clicked test. 
        """
        name = self.sender().unittest_name
        self.runTests({name : self.buttons[name]})
        # print(self.plot_canvas.figure.)

    def runTests(self, buttons):
        """ Run the unit tests corresponding to the dict buttons.
        Buttons are used instead of names because the isChecked() value provides
        pass/fail information about the test, whereas a name is just...a name.
        """
        suite = unittest.TestSuite()
        stream = io.StringIO()

        for name, button in buttons.items():
            suite.addTest(self.unittest_class(name))

        runner = unittest.TextTestRunner(
            stream=stream, 
            verbosity=2, 
            failfast=True,
            resultclass=TextTestResult,
        )
        runner.run(suite)


if __name__ == '__main__':

    # Uses statically defined plot canvas, no subclassing
    class TestPage1(unittest.TestCase):

        PLOT_COLS = 1
        PLOT_SIZE = (12, 6)
        plot_canvas = PlotCanvas(size=PLOT_SIZE, cols=PLOT_COLS)

        def test_01(self):
            x = np.linspace(0,10,50)
            self.plot_canvas.plot(x, 2*x)

    class TestPage1b(unittest.TestCase):

        PLOT_COLS = 1
        PLOT_SIZE = (12, 6)
        plot_canvas = PlotCanvas(size=PLOT_SIZE, cols=PLOT_COLS)

        def test_01(self):
            x = np.linspace(0,10,50)
            self.plot_canvas.plot(x, 2*x)

    # Uses statically defined canvas in subclass. Should act the same way as TestPage1.
    class TestPage2(StaticPlottingTestCase):

        def test_01(self):
            x = np.linspace(0,10,50)
            self.plot_canvas.plot(x, 3*x)

    class TestPage2b(StaticPlottingTestCase):

        def test_01(self):
            x = np.linspace(0,10,50)
            self.plot_canvas.plot(x, 3*x)

    # Uses instance defined canvas. This doesn't plot anything.
    class TestPage3(InstancePlottingTestCase):

        def test_01(self):
            x = np.linspace(0,10,50)
            y = x ^ 2
            self.plot_canvas.plot(x, y)

    class TestPage3b(InstancePlottingTestCase):

        def test_01(self):
            x = np.linspace(0,10,50)
            y = x ^ 2
            self.plot_canvas.plot(x, y)

    app = QtWidgets.QApplication.instance()
    if app is None:
        app = QtWidgets.QApplication(sys.argv)

    print('Conjuring test wizard...')
    wizard = QWizard()
    wizard.setWizardStyle(QtWidgets.QWizard.ModernStyle)

    # Debugging
    DEBUG_STATE = 6

    # Adding TestPage1 works as expected. The plots are independent.
    if DEBUG_STATE == 1:
        wizard.addPage(TestPage(TestPage1, static_canvas=True))
        wizard.addPage(TestPage(TestPage1b, static_canvas=True))

    # A single TestPage2 works...
    elif DEBUG_STATE == 2:
        wizard.addPage(TestPage(TestPage2, static_canvas=True))

    # ...but adding two of them does not work...
    # The canvas doesn't show up on the first page. 
    # I think this has something to do with the widgets layout.
    elif DEBUG_STATE == 3:
        wizard.addPage(TestPage(TestPage2, static_canvas=True))
        wizard.addPage(TestPage(TestPage2b, static_canvas=True))

    # Making self.canvas a class variable generates the plot, but now plotting doesn't do anything
    elif DEBUG_STATE == 4:
        wizard.addPage(TestPage(TestPage3, static_canvas=False))
        wizard.addPage(TestPage(TestPage3b, static_canvas=False))

    # Plotting works on the statically defined canvas, but not on the class-defined one.
    elif DEBUG_STATE == 5:
        wizard.addPage(TestPage(TestPage2, static_canvas=True))
        wizard.addPage(TestPage(TestPage3, static_canvas=False))

    # A clue! Adding a page with the same name mimics DEBUG_STATE == 3...
    elif DEBUG_STATE == 6:
        wizard.addPage(TestPage(TestPage1, static_canvas=True))
        wizard.addPage(TestPage(TestPage1, static_canvas=True))


    wizard.setFixedSize(1200, 800)
    wizard.show()
    app.exec_()

0 个答案:

没有答案