import sys
import numpy as np
from qwt.qt.QtGui import (QApplication, QPen, QBrush, QFrame, QFont, QWidget,
QMainWindow, QToolButton, QIcon, QPixmap, QToolBar,
QHBoxLayout, QLabel, QPrinter, QPrintDialog,
QFontDatabase)
from qwt.qt.QtCore import QSize
from qwt.qt.QtCore import Qt
from qwt import (QwtPlot, QwtPlotMarker, QwtSymbol, QwtLegend, QwtPlotGrid,
QwtPlotCurve, QwtPlotItem, QwtLogScaleEngine, QwtText,
QwtPlotRenderer)
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
self.grid = QwtPlotGrid()
self.grid.enableXMin(True)
self.grid.attach(self)
# 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('Amplitude')
self.curve1.setRenderHint(QwtPlotItem.RenderAntialiased);
self.curve1.setPen(QPen(Qt.yellow))
self.curve1.setYAxis(QwtPlot.yLeft)
self.curve1.attach(self)
self.curve2 = QwtPlotCurve('Phase')
self.curve2.setRenderHint(QwtPlotItem.RenderAntialiased);
self.curve2.setPen(QPen(Qt.cyan))
self.curve2.setYAxis(QwtPlot.yRight)
self.curve2.attach(self)
# alias
fn = self.fontInfo().family()
# marker
self.dB3Marker = m = QwtPlotMarker()
m.setValue(0.0, 0.0)
m.setLineStyle(QwtPlotMarker.VLine)
m.setLabelAlignment(Qt.AlignRight | Qt.AlignBottom)
m.setLinePen(QPen(Qt.green, 2, Qt.DashDotLine))
text = QwtText('')
text.setColor(Qt.green)
text.setBackgroundBrush(Qt.red)
text.setFont(QFont(fn, 12, QFont.Bold))
m.setLabel(text)
m.attach(self)
self.peakMarker = m = QwtPlotMarker()
m.setLineStyle(QwtPlotMarker.HLine)
m.setLabelAlignment(Qt.AlignRight | Qt.AlignBottom)
m.setLinePen(QPen(Qt.red, 2, Qt.DashDotLine))
text = QwtText('')
text.setColor(Qt.red)
text.setBackgroundBrush(QBrush(self.canvasBackground()))
text.setFont(QFont(fn, 12, QFont.Bold))
m.setLabel(text)
m.setSymbol(QwtSymbol(QwtSymbol.Diamond,
QBrush(Qt.yellow),
QPen(Qt.green),
QSize(7,7)))
m.attach(self)
# text marker
m = QwtPlotMarker()
m.setValue(0.1, -20.0)
m.setLabelAlignment(Qt.AlignRight | Qt.AlignBottom)
text = QwtText(
'[1-(\u03c9/\u03c9<sub>0</sub>)<sup>2</sup>+2j\u03c9/Q]'
'<sup>-1</sup>'
)
text.setFont(QFont(fn, 12, QFont.Bold))
text.setColor(Qt.blue)
text.setBackgroundBrush(QBrush(Qt.yellow))
text.setBorderPen(QPen(Qt.red, 2))
m.setLabel(text)
m.attach(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()
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()
def print_(self):
printer = QPrinter(QPrinter.HighResolution)
printer.setCreator('Bode example')
printer.setOrientation(QPrinter.Landscape)
printer.setColorMode(QPrinter.Color)
docName = str(self.plot.title().text())
if not docName:
docName.replace('\n', ' -- ')
printer.setDocName(docName)
dialog = QPrintDialog(printer)
if dialog.exec_():
renderer = QwtPlotRenderer()
if (QPrinter.GrayScale == printer.colorMode()):
renderer.setDiscardFlag(QwtPlotRenderer.DiscardBackground)
renderer.setDiscardFlag(QwtPlotRenderer.DiscardCanvasBackground)
renderer.setDiscardFlag(QwtPlotRenderer.DiscardCanvasFrame)
renderer.setLayoutFlag(QwtPlotRenderer.FrameWithScales)
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 make():
demo = BodeDemo()
demo.resize(540, 400)
demo.show()
return demo
if __name__ == '__main__':
app = QApplication(sys.argv)
fonts = QFontDatabase()
for name in ('Verdana', 'STIXGeneral'):
if name in fonts.families():
app.setFont(QFont(name))
break
demo = make()
sys.exit(app.exec_())