# Fourier_series.py # OVERVIEW # This program demonstrates the use of the matplotlib graphical toolkit in # conjunction with the Qt Graphical User Interface (GUI) API. matplotlib # provides an extensive set of functions for generating various types of 2-D and # 3-D graphics, but does not support user interaction. When writing a Python # program that produces graphical output and must also handle user interaction # (via keyboard and/or mouse), there are at least two basic approaches: # (1) Use matplotlib together with a GUI such as Qt or Wx. [I used Wx at one # time, but have been migrating to Qt, which is more powerful and is rapidly # supplanting Wx.] # (2) Use Chaco. "Chaco is a Python plotting application toolkit that # facilitates writing plotting applications at all levels of complexity, from # simple scripts with hard-coded data to large plotting programs with complex # data interrelationships and a multitude of interactive tools. While Chaco # generates attractive static plots for publication and presentation, it also # works well for interactive data visualization and exploration." # For simple user interactions, (1) is the preferred approach. # This program generates successive Fourier series approximations to a square # wave, triangle wave, sawtooth wave, full-wave rectified sine wave, or # half-wave rectified sine wave. The program recognizes the following keys: # right arrow: increment the truncation order # left arrow: decrement the truncation order # down arrow: next waveform # up arrow: previous waveform # Escape key or 'Q': terminates the program # AUTHOR # Dr. Phillip M. Feldman # LAST UPDATE # Sept. 1, 2013 # ACKNOWLEDGMENT # I received many helpful suggestions from Robert Kern of Enthought. import sys from numpy import arange, cos, pi, sin, zeros # Import the Figure Matplotlib object: this is the backend-independent # representation of our plot: from matplotlib.figure import Figure # Import from the matplotlib.backends.backend_qt4agg module the # FigureCanvasQTAgg class, which is the backend-dependent figure canvas: from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg from PySide.QtCore import * # Python Qt4 bindings for GUI objects: from PySide import QtGui class MainWindow(QtGui.QWidget): def __init__(self, parent=None): global canvas QtGui.QWidget.__init__(self, parent=parent) self.setGeometry(50, 100, 900, 750) self.setWindowTitle("Fourier series Qt demo") self.large_font= QtGui.QFont( "SansSerif", 20, QtGui.QFont.Bold ) # The focus policy must be one of the following: # - Qt.TabFocus: the widget accepts focus by tabbing # - Qt.ClickFocus: the widget accepts focus by clicking # - Qt.StrongFocus: the widget accepts focus by clicking or tabbing # - Qt.NoFocus (the default): it does not accept focus at all self.setFocusPolicy(Qt.StrongFocus) # Create the Matplotlib widget canvas= FigureCanvas(parent=self) # Note that the `show` method does not force a redraw. It just # makes the widget visible if it was hidden. canvas.show() def keyPressEvent(self, event): """ This method is called whenever a key is pressed. It receives a QKeyEvent object as a parameter. """ global order, waveform self.code_of_last_pressed_key= event.key() # If the user has pressed the Escape key or 'q', terminate the program: if self.code_of_last_pressed_key in [Qt.Key_Escape, Qt.Key_Q]: # The following statement queues an event that will terminate the # application, but does not immediately stop execution. Without the # `return`, execution of this method would continue. QCoreApplication.exit(0) return if self.code_of_last_pressed_key == Qt.Key_Right: # Increment `order` by 2 or by 4, looping back to 1 if more than 63: if order <= 13: order+= 2 else: order+= 4 if order > 63: order= 1 elif self.code_of_last_pressed_key == Qt.Key_Left: # Decrement `order` by 2 or by 4, looping back to 63 if less than 1: if order <= 13: order-= 2 else: order-= 4 if order < 1: order= 63 elif self.code_of_last_pressed_key == Qt.Key_Down: # Advance to the next waveform: waveform= (waveform+1) % 5 elif self.code_of_last_pressed_key == Qt.Key_Up: # Return to the previous waveform: waveform= (waveform-1) % 5 else: # The user has pressed an illegal key. QtGui.QSound("bell.wav").play() # Clear the axes and display instructions: canvas.axes.clear() canvas.axes.annotate( 'Keys:\n\n' 'right arrow: increment truncation order\n' 'left arrow: decrement truncation order\n' 'down arrow: next waveform\n' 'up arrow: previous waveform\n' 'Esc or Q : terminate the program', size=20, family='monospace', fontweight='bold', xy=(0.15, 0.6), xycoords='axes fraction', xytext=(-50, 30), textcoords='offset points' ) canvas.draw() return # Recalculate the plot and update the display: t= arange(0.0, 2.0, 0.01) # Calculate sum of Fourier series terms up to and including `order`: y= zeros(t.size) if waveform == 0: # square wave: for k in range(1, order+1, 2): y+= sin(2*pi*k*t) / k y*= 4./pi elif waveform == 1: # triangle wave: sign= 1 for k in range(1, order+1, 2): y+= sign * sin(2*pi*k*t) / (k*k) sign= -sign y*= 8./(pi*pi) elif waveform == 2: # sawtooth: for k in range(1, order+1): y+= sin(2*pi*k*t) / k y= 0.4 - y/pi elif waveform == 3: # full-wave rectified sine wave: for k in range(1, order+1): y+= cos(2*pi*k*t) / (4*k*k-1) y= (2.0 - 4.*y) / pi else: # half-wave rectified sine wave: for k in range(2, order+1, 2): y+= cos(2*pi*k*t) / (k*k-1) y= 0.5*sin(2*pi*t) + (1.0 - 2.*y) / pi canvas.axes.clear() canvas.axes.plot(t, y, linewidth=5) canvas.axes.set_title("Fourier series approx. to %s wave, order=%d" % (['square', 'triangle', 'sawtooth', 'full-wave rectified sine', 'half-wave rectified sine'][waveform], order), fontsize=18) canvas.axes.grid(True, linestyle='--', linewidth=2) # Set axis limits so that these do not vary with the truncation order: if waveform == 2: canvas.axes.set_ylim([-0.2, 1.0]) elif waveform == 3: canvas.axes.set_ylim([ 0.0, 1.1]) elif waveform == 4: canvas.axes.set_ylim([-0.2, 1.1]) # Tell canvas that it needs to repaint itself: canvas.draw() def keyReleaseEvent(self, event): pass class FigureCanvas(FigureCanvasQTAgg): """ Class to represent the FigureCanvas widget """ def __init__(self, parent=None): # Generate a Matplotlib figure with a single `axes` object that fills the # entire figure: self.fig= Figure(facecolor=[1, 1, 1], figsize=(12,9)) self.axes= self.fig.add_subplot(111) # Initialize the canvas into which Figure renders: FigureCanvasQTAgg.__init__(self, self.fig) # Make this widget a child of the main window. [It is curious to me that # one can change one's parent after the fact.] self.setParent(parent) self.axes.annotate( 'Keys:\n\n' 'right arrow: increment truncation order\n' 'left arrow: decrement truncation order\n' 'down arrow: next waveform\n' 'up arrow: previous waveform\n' 'Esc or Q : terminate the program', size=20, family='monospace', fontweight='bold', xy=(0.15, 0.6), xycoords='axes fraction', xytext=(-50, 30), textcoords='offset points' ) self.draw() # Initially there is no canvas object: canvas= None # Initialize `order` (expansion order) and `waveform`: order= -1 waveform= 0 # Launch the main program: application= QtGui.QApplication(sys.argv) application_window= MainWindow() application_window.show() application.exec_()