Bode demo — PythonQwt 0.14 Manual (original) (raw)

../_images/bodedemo.png

import os

import numpy as np from qtpy.QtCore import Qt from qtpy.QtGui import QFont, QIcon, QPageLayout, QPen, QPixmap from qtpy.QtPrintSupport import QPrintDialog, QPrinter from qtpy.QtWidgets import ( QFrame, QHBoxLayout, QLabel, QMainWindow, QToolBar, QToolButton, QWidget, )

from qwt import ( QwtLegend, QwtLogScaleEngine, QwtPlot, QwtPlotCurve, QwtPlotGrid, QwtPlotMarker, QwtPlotRenderer, QwtSymbol, QwtText, ) from qwt.tests import utils

print_xpm = [ "32 32 12 1", "a c #ffffff", "h c #ffff00", "c c #ffffff", "f c #dcdcdc", "b c #c0c0c0", "j c #a0a0a4", "e c #808080", "g c #808000", "d c #585858", "i c #00ff00", "# c #000000", ". c None", "................................", "................................", "...........###..................", "..........#abb###...............", ".........#aabbbbb###............", ".........#ddaaabbbbb###.........", "........#ddddddaaabbbbb###......", ".......#deffddddddaaabbbbb###...", "......#deaaabbbddddddaaabbbbb###", ".....#deaaaaaaabbbddddddaaabbbb#", "....#deaaabbbaaaa#ddedddfggaaad#", "...#deaaaaaaaaaa#ddeeeeafgggfdd#", "..#deaaabbbaaaa#ddeeeeabbbbgfdd#", ".#deeefaaaaaaa#ddeeeeabbhhbbadd#", "#aabbbeeefaaa#ddeeeeabbbbbbaddd#", "#bbaaabbbeee#ddeeeeabbiibbadddd#", "#bbbbbaaabbbeeeeeeabbbbbbaddddd#", "#bjbbbbbbaaabbbbeabbbbbbadddddd#", "#bjjjjbbbbbbaaaeabbbbbbaddddddd#", "#bjaaajjjbbbbbbaaabbbbadddddddd#", "#bbbbbaaajjjbbbbbbaaaaddddddddd#", "#bjbbbbbbaaajjjbbbbbbddddddddd#.", "#bjjjjbbbbbbaaajjjbbbdddddddd#..", "#bjaaajjjbbbbbbjaajjbddddddd#...", "#bbbbbaaajjjbbbjbbaabdddddd#....", "###bbbbbbaaajjjjbbbbbddddd#.....", "...###bbbbbbaaajbbbbbdddd#......", "......###bbbbbbjbbbbbddd#.......", ".........###bbbbbbbbbdd#........", "............###bbbbbbd#.........", "...............###bbb#..........", "..................###...........", ]

class BodePlot(QwtPlot): def init(self, *args): QwtPlot.init(self, *args)

    self.setTitle("Frequency Response of a 2<sup>nd</sup>-order System")
    self.setCanvasBackground(Qt.darkBlue)

    # legend
    legend = QwtLegend()
    legend.setFrameStyle(QFrame.Box | QFrame.Sunken)
    self.insertLegend(legend, QwtPlot.BottomLegend)

    # grid
    QwtPlotGrid.make(plot=self, enableminor=(True, False), color=Qt.darkGray)

    # axes
    self.enableAxis(QwtPlot.yRight)
    self.setAxisTitle(QwtPlot.xBottom, "\u03c9/\u03c9<sub>0</sub>")
    self.setAxisTitle(QwtPlot.yLeft, "Amplitude [dB]")
    self.setAxisTitle(QwtPlot.yRight, "Phase [\u00b0]")

    self.setAxisMaxMajor(QwtPlot.xBottom, 6)
    self.setAxisMaxMinor(QwtPlot.xBottom, 10)
    self.setAxisScaleEngine(QwtPlot.xBottom, QwtLogScaleEngine())

    # curves
    self.curve1 = QwtPlotCurve.make(
        title="Amplitude", linecolor=Qt.yellow, plot=self, antialiased=True
    )
    self.curve2 = QwtPlotCurve.make(
        title="Phase", linecolor=Qt.cyan, plot=self, antialiased=True
    )
    self.dB3Marker = QwtPlotMarker.make(
        label=QwtText.make(color=Qt.white, brush=Qt.red, weight=QFont.Light),
        linestyle=QwtPlotMarker.VLine,
        align=Qt.AlignRight | Qt.AlignBottom,
        color=Qt.green,
        width=2,
        style=Qt.DashDotLine,
        plot=self,
    )
    self.peakMarker = QwtPlotMarker.make(
        label=QwtText.make(
            color=Qt.red, brush=self.canvasBackground(), weight=QFont.Bold
        ),
        symbol=QwtSymbol.make(QwtSymbol.Diamond, Qt.yellow, Qt.green, (7, 7)),
        linestyle=QwtPlotMarker.HLine,
        align=Qt.AlignRight | Qt.AlignBottom,
        color=Qt.red,
        width=2,
        style=Qt.DashDotLine,
        plot=self,
    )
    QwtPlotMarker.make(
        xvalue=0.1,
        yvalue=-20.0,
        align=Qt.AlignRight | Qt.AlignBottom,
        label=QwtText.make(
            "[1-(\u03c9/\u03c9<sub>0</sub>)<sup>2</sup>+2j\u03c9/Q]"
            "<sup>-1</sup>",
            color=Qt.white,
            borderradius=2,
            borderpen=QPen(Qt.lightGray, 5),
            brush=Qt.lightGray,
            weight=QFont.Bold,
        ),
        plot=self,
    )

    self.setDamp(0.01)

def showData(self, frequency, amplitude, phase):
    self.curve1.setData(frequency, amplitude)
    self.curve2.setData(frequency, phase)

def showPeak(self, frequency, amplitude):
    self.peakMarker.setValue(frequency, amplitude)
    label = self.peakMarker.label()
    label.setText("Peak: %4g dB" % amplitude)
    self.peakMarker.setLabel(label)

def show3dB(self, frequency):
    self.dB3Marker.setValue(frequency, 0.0)
    label = self.dB3Marker.label()
    label.setText("-3dB at f = %4g" % frequency)
    self.dB3Marker.setLabel(label)

def setDamp(self, d):
    self.damping = d
    # Numerical Python: f, g, a and p are NumPy arrays!
    f = np.exp(np.log(10.0) * np.arange(-2, 2.02, 0.04))
    g = 1.0 / (1.0 - f * f + 2j * self.damping * f)
    a = 20.0 * np.log10(abs(g))
    p = 180 * np.arctan2(g.imag, g.real) / np.pi
    # for show3dB
    i3 = np.argmax(np.where(np.less(a, -3.0), a, -100.0))
    f3 = f[i3] - (a[i3] + 3.0) * (f[i3] - f[i3 - 1]) / (a[i3] - a[i3 - 1])
    # for showPeak
    imax = np.argmax(a)

    self.showPeak(f[imax], a[imax])
    self.show3dB(f3)
    self.showData(f, a, p)

    self.replot()

FNAME_PDF = "bode.pdf"

class BodeDemo(QMainWindow): def init(self, *args): QMainWindow.init(self, *args)

    self.plot = BodePlot(self)
    self.plot.setContentsMargins(5, 5, 5, 0)

    self.setContextMenuPolicy(Qt.NoContextMenu)

    self.setCentralWidget(self.plot)

    toolBar = QToolBar(self)
    self.addToolBar(toolBar)

    btnPrint = QToolButton(toolBar)
    btnPrint.setText("Print")
    btnPrint.setIcon(QIcon(QPixmap(print_xpm)))
    btnPrint.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
    toolBar.addWidget(btnPrint)
    btnPrint.clicked.connect(self.print_)

    btnExport = QToolButton(toolBar)
    btnExport.setText("Export")
    btnExport.setIcon(QIcon(QPixmap(print_xpm)))
    btnExport.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
    toolBar.addWidget(btnExport)
    btnExport.clicked.connect(self.exportDocument)

    toolBar.addSeparator()

    dampBox = QWidget(toolBar)
    dampLayout = QHBoxLayout(dampBox)
    dampLayout.setSpacing(0)
    dampLayout.addWidget(QWidget(dampBox), 10)  # spacer
    dampLayout.addWidget(QLabel("Damping Factor", dampBox), 0)
    dampLayout.addSpacing(10)

    toolBar.addWidget(dampBox)

    self.statusBar()

    self.showInfo()

    if utils.TestEnvironment().unattended:
        self.print_(unattended=True)

def print_(self, unattended=False):
    try:
        mode = QPrinter.HighResolution
        printer = QPrinter(mode)
    except AttributeError:
        # Some PySide6 / PyQt6 versions do not have this attribute on Linux
        printer = QPrinter()

    printer.setCreator("Bode example")
    printer.setPageOrientation(QPageLayout.Landscape)
    try:
        printer.setColorMode(QPrinter.Color)
    except AttributeError:
        pass

    docName = str(self.plot.title().text())
    if not docName:
        docName.replace("\n", " -- ")
        printer.setDocName(docName)

    dialog = QPrintDialog(printer)
    if unattended:
        # Configure QPrinter object to print to PDF file
        printer.setPrinterName("")
        printer.setOutputFileName(FNAME_PDF)
        dialog.accept()
        ok = True
    else:
        ok = dialog.exec_()
    if ok:
        renderer = QwtPlotRenderer()
        renderer.renderTo(self.plot, printer)

def exportDocument(self):
    renderer = QwtPlotRenderer(self.plot)
    renderer.exportTo(self.plot, "bode")

def showInfo(self, text=""):
    self.statusBar().showMessage(text)

def moved(self, point):
    info = "Freq=%g, Ampl=%g, Phase=%g" % (
        self.plot.invTransform(QwtPlot.xBottom, point.x()),
        self.plot.invTransform(QwtPlot.yLeft, point.y()),
        self.plot.invTransform(QwtPlot.yRight, point.y()),
    )
    self.showInfo(info)

def selected(self, _):
    self.showInfo()

def test_bodedemo(): """Bode demo""" utils.test_widget(BodeDemo, (640, 480)) if os.path.isfile(FNAME_PDF): os.remove(FNAME_PDF)

if name == "main": test_bodedemo()