.. _user_interfaces-fourier_demo_wx: user_interfaces example code: fourier_demo_wx.py ================================================ [`source code `_] :: import numpy as np import wx import matplotlib matplotlib.interactive(False) matplotlib.use('WXAgg') from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg from matplotlib.figure import Figure from matplotlib.pyplot import gcf, setp class Knob: """ Knob - simple class with a "setKnob" method. A Knob instance is attached to a Param instance, e.g., param.attach(knob) Base class is for documentation purposes. """ def setKnob(self, value): pass class Param: """ The idea of the "Param" class is that some parameter in the GUI may have several knobs that both control it and reflect the parameter's state, e.g. a slider, text, and dragging can all change the value of the frequency in the waveform of this example. The class allows a cleaner way to update/"feedback" to the other knobs when one is being changed. Also, this class handles min/max constraints for all the knobs. Idea - knob list - in "set" method, knob object is passed as well - the other knobs in the knob list have a "set" method which gets called for the others. """ def __init__(self, initialValue=None, minimum=0., maximum=1.): self.minimum = minimum self.maximum = maximum if initialValue != self.constrain(initialValue): raise ValueError('illegal initial value') self.value = initialValue self.knobs = [] def attach(self, knob): self.knobs += [knob] def set(self, value, knob=None): self.value = value self.value = self.constrain(value) for feedbackKnob in self.knobs: if feedbackKnob != knob: feedbackKnob.setKnob(self.value) return self.value def constrain(self, value): if value <= self.minimum: value = self.minimum if value >= self.maximum: value = self.maximum return value class SliderGroup(Knob): def __init__(self, parent, label, param): self.sliderLabel = wx.StaticText(parent, label=label) self.sliderText = wx.TextCtrl(parent, -1, style=wx.TE_PROCESS_ENTER) self.slider = wx.Slider(parent, -1) self.slider.SetMax(param.maximum*1000) self.setKnob(param.value) sizer = wx.BoxSizer(wx.HORIZONTAL) sizer.Add(self.sliderLabel, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, border=2) sizer.Add(self.sliderText, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, border=2) sizer.Add(self.slider, 1, wx.EXPAND) self.sizer = sizer self.slider.Bind(wx.EVT_SLIDER, self.sliderHandler) self.sliderText.Bind(wx.EVT_TEXT_ENTER, self.sliderTextHandler) self.param = param self.param.attach(self) def sliderHandler(self, evt): value = evt.GetInt() / 1000. self.param.set(value) def sliderTextHandler(self, evt): value = float(self.sliderText.GetValue()) self.param.set(value) def setKnob(self, value): self.sliderText.SetValue('%g'%value) self.slider.SetValue(value*1000) class FourierDemoFrame(wx.Frame): def __init__(self, *args, **kwargs): wx.Frame.__init__(self, *args, **kwargs) self.fourierDemoWindow = FourierDemoWindow(self) self.frequencySliderGroup = SliderGroup(self, label='Frequency f0:', \ param=self.fourierDemoWindow.f0) self.amplitudeSliderGroup = SliderGroup(self, label=' Amplitude a:', \ param=self.fourierDemoWindow.A) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.fourierDemoWindow, 1, wx.EXPAND) sizer.Add(self.frequencySliderGroup.sizer, 0, \ wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, border=5) sizer.Add(self.amplitudeSliderGroup.sizer, 0, \ wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, border=5) self.SetSizer(sizer) class FourierDemoWindow(wx.Window, Knob): def __init__(self, *args, **kwargs): wx.Window.__init__(self, *args, **kwargs) self.lines = [] self.figure = Figure() self.canvas = FigureCanvasWxAgg(self, -1, self.figure) self.canvas.callbacks.connect('button_press_event', self.mouseDown) self.canvas.callbacks.connect('motion_notify_event', self.mouseMotion) self.canvas.callbacks.connect('button_release_event', self.mouseUp) self.state = '' self.mouseInfo = (None, None, None, None) self.f0 = Param(2., minimum=0., maximum=6.) self.A = Param(1., minimum=0.01, maximum=2.) self.draw() # Not sure I like having two params attached to the same Knob, # but that is what we have here... it works but feels kludgy - # although maybe it's not too bad since the knob changes both params # at the same time (both f0 and A are affected during a drag) self.f0.attach(self) self.A.attach(self) self.Bind(wx.EVT_SIZE, self.sizeHandler) def sizeHandler(self, *args, **kwargs): self.canvas.SetSize(self.GetSize()) def mouseDown(self, evt): if self.lines[0] in self.figure.hitlist(evt): self.state = 'frequency' elif self.lines[1] in self.figure.hitlist(evt): self.state = 'time' else: self.state = '' self.mouseInfo = (evt.xdata, evt.ydata, max(self.f0.value, .1), self.A.value) def mouseMotion(self, evt): if self.state == '': return x, y = evt.xdata, evt.ydata if x is None: # outside the axes return x0, y0, f0Init, AInit = self.mouseInfo self.A.set(AInit+(AInit*(y-y0)/y0), self) if self.state == 'frequency': self.f0.set(f0Init+(f0Init*(x-x0)/x0)) elif self.state == 'time': if (x-x0)/x0 != -1.: self.f0.set(1./(1./f0Init+(1./f0Init*(x-x0)/x0))) def mouseUp(self, evt): self.state = '' def draw(self): if not hasattr(self, 'subplot1'): self.subplot1 = self.figure.add_subplot(211) self.subplot2 = self.figure.add_subplot(212) x1, y1, x2, y2 = self.compute(self.f0.value, self.A.value) color = (1., 0., 0.) self.lines += self.subplot1.plot(x1, y1, color=color, linewidth=2) self.lines += self.subplot2.plot(x2, y2, color=color, linewidth=2) #Set some plot attributes self.subplot1.set_title("Click and drag waveforms to change frequency and amplitude", fontsize=12) self.subplot1.set_ylabel("Frequency Domain Waveform X(f)", fontsize = 8) self.subplot1.set_xlabel("frequency f", fontsize = 8) self.subplot2.set_ylabel("Time Domain Waveform x(t)", fontsize = 8) self.subplot2.set_xlabel("time t", fontsize = 8) self.subplot1.set_xlim([-6, 6]) self.subplot1.set_ylim([0, 1]) self.subplot2.set_xlim([-2, 2]) self.subplot2.set_ylim([-2, 2]) self.subplot1.text(0.05, .95, r'$X(f) = \mathcal{F}\{x(t)\}$', \ verticalalignment='top', transform = self.subplot1.transAxes) self.subplot2.text(0.05, .95, r'$x(t) = a \cdot \cos(2\pi f_0 t) e^{-\pi t^2}$', \ verticalalignment='top', transform = self.subplot2.transAxes) def compute(self, f0, A): f = np.arange(-6., 6., 0.02) t = np.arange(-2., 2., 0.01) x = A*np.cos(2*np.pi*f0*t)*np.exp(-np.pi*t**2) X = A/2*(np.exp(-np.pi*(f-f0)**2) + np.exp(-np.pi*(f+f0)**2)) return f, X, t, x def repaint(self): self.canvas.draw() def setKnob(self, value): # Note, we ignore value arg here and just go by state of the params x1, y1, x2, y2 = self.compute(self.f0.value, self.A.value) setp(self.lines[0], xdata=x1, ydata=y1) setp(self.lines[1], xdata=x2, ydata=y2) self.repaint() class App(wx.App): def OnInit(self): self.frame1 = FourierDemoFrame(parent=None, title="Fourier Demo", size=(640, 480)) self.frame1.Show() return True app = App() app.MainLoop() Keywords: python, matplotlib, pylab, example, codex (see :ref:`how-to-search-examples`)