# 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_()