.. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_gallery_user_interfaces_fourier_demo_wx_sgskip.py: =============== Fourier Demo WX =============== .. code-block:: default import numpy as np import wx from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas from matplotlib.figure import Figure class Knob(object): """ 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(object): """ 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.slider.SetRange(0, 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) panel = wx.Panel(self) # create the GUI elements self.createCanvas(panel) self.createSliders(panel) # place them in a sizer for the Layout sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.canvas, 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) panel.SetSizer(sizer) def createCanvas(self, parent): self.lines = [] self.figure = Figure() self.canvas = FigureCanvas(parent, -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.createPlots() # 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) def createSliders(self, panel): self.frequencySliderGroup = SliderGroup( panel, label='Frequency f0:', param=self.f0) self.amplitudeSliderGroup = SliderGroup(panel, label=' Amplitude a:', param=self.A) def mouseDown(self, evt): if self.lines[0].contains(evt)[0]: self.state = 'frequency' elif self.lines[1].contains(evt)[0]: 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 createPlots(self): # This method creates the subplots, waveforms and labels. # Later, when the waveforms or sliders are dragged, only the # waveform data will be updated (not here, but below in setKnob). self.subplot1, self.subplot2 = self.figure.subplots(2) 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 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) # update the data of the two waveforms self.lines[0].set(xdata=x1, ydata=y1) self.lines[1].set(xdata=x2, ydata=y2) # make the canvas draw its contents again with the new data self.canvas.draw() 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() .. _sphx_glr_download_gallery_user_interfaces_fourier_demo_wx_sgskip.py: .. only :: html .. container:: sphx-glr-footer :class: sphx-glr-footer-example .. container:: sphx-glr-download :download:`Download Python source code: fourier_demo_wx_sgskip.py ` .. container:: sphx-glr-download :download:`Download Jupyter notebook: fourier_demo_wx_sgskip.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature Keywords: matplotlib code example, codex, python plot, pyplot `Gallery generated by Sphinx-Gallery `_