class wxMatplotPanelSimple(wx.Panel): def __init__(self, renderPanel, color=None, dpi=None, **kwargs): from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg from matplotlib.figure import Figure # initialize Panel if 'id' not in list(kwargs.keys()): kwargs['id'] = wx.ID_ANY if 'style' not in list(kwargs.keys()): kwargs['style'] = wx.NO_FULL_REPAINT_ON_RESIZE wx.Panel.__init__(self, renderPanel, **kwargs) self.figure = Figure(None, dpi) #self.canvas = NoRepaintCanvas( self, -1, self.figure ) self.canvas = FigureCanvasWxAgg(self, -1, self.figure) sizer = wx.BoxSizer() sizer.Add(self.canvas, 1, wx.EXPAND) self.SetSizer(sizer) self.axes = self.figure.add_subplot(111) self.axes.set_aspect('auto') self.Bind(wx.EVT_SIZE, self._onSize) def _onSize(self, event=None): pixels = tuple([self.GetSize()[0], self.GetSize()[1]]) print(pixels) # if self.canvas.GetMinSize( )[0] != pixels[0] or \ # self.canvas.GetMinSize( )[1] != pixels[1] : self.canvas.SetMinSize(pixels) self.figure.set_size_inches( float(pixels[0]) / self.figure.get_dpi(), float(pixels[1]) / self.figure.get_dpi()) self.canvas.draw()
def drawPlot(self, figureArray): for indx in reversed(range(len(self.plotBox.GetChildren()))): self.plotBox.GetItem(indx).DeleteWindows() self.plotBox.Remove(indx) for fig in figureArray: plot = FigureCanvas(self, -1, fig) plot.SetMinSize((200, 200)) self.plotBox.Add(plot, 1, wx.SHAPED, 0) self.Layout()
class ColourPickerPanel(StylePickerPanel): """ Based on StylePickerPanel, a Panel for selecting a list of colours, and their order """ def __init__( self, parent: wx.Window, id: wx.WindowID = wx.ID_ANY, pos: wx.Point = wx.DefaultPosition, size: wx.Size = wx.DefaultSize, style: int = wx.TAB_TRAVERSAL, name: str = wx.PanelNameStr, label: str = "Choose Colours: ", picker_choices: List[str] = None, selection_choices: List[str] = None, ): """ :param parent: The parent window. :type parent: wx.Window :param id: An identifier for the panel. wx.ID_ANY is taken to mean a default. :type id: wx.WindowID, optional :param pos: The panel position. The value ::wxDefaultPosition indicates a default position, chosen by either the windowing system or wxWidgets, depending on platform. :type pos: wx.Point, optional :param size: The panel size. The value ::wxDefaultSize indicates a default size, chosen by either the windowing system or wxWidgets, depending on platform. :type size: wx.Size, optional :param style: The window style. See wxPanel. :type style: int, optional :param name: Window name. :type name: str, optional :param label: Label for the panel :type label: str, optional :param picker_choices: A list of hex value choices to populate the 'picker' size of the panel with :type picker_choices: list of str :param selection_choices: A list of hex value choices to populate the 'selection' size of the panel with :type selection_choices: list of str """ args = (parent, id, pos, size) kwds = {"style": style, "name": name} if picker_choices is None: picker_choices = default_picker_choices if selection_choices is None: selection_choices = default_colours[:] self.label = label self.picker_choices = picker_choices self.selection_choices = selection_choices # begin wxGlade: ColourPickerPanel.__init__ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL wx.Panel.__init__(self, *args, **kwds) self.main_panel = wx.Panel(self, wx.ID_ANY) self.move_panel = wx.Panel(self.main_panel, wx.ID_ANY) self.picker_list_box = wx.ListBox(self.main_panel, wx.ID_ANY, choices=[]) self.picker_figure = Figure() self.picker_canvas = FigureCanvas(self.main_panel, wx.ID_ANY, self.picker_figure) self.add_btn = wx.Button(self.main_panel, wx.ID_ANY, u"Add 🡲") self.remove_btn = wx.Button(self.main_panel, wx.ID_ANY, u"🡰 Remove") self.selection_list_box = wx.ListBox(self.main_panel, wx.ID_ANY, choices=[]) self.up_btn = wx.Button(self.main_panel, wx.ID_ANY, u"🡱 Up") self.down_btn = wx.Button(self.main_panel, wx.ID_ANY, u"🡳 Down") self.selection_figure = Figure() self.selection_canvas = FigureCanvas(self.main_panel, wx.ID_ANY, self.selection_figure) self.__set_properties() self.__do_layout() # end wxGlade self.Bind(wx.EVT_LISTBOX, self.update_picker_preview, self.picker_list_box) self.Bind(wx.EVT_LISTBOX_DCLICK, self.add, self.picker_list_box) self.Bind(wx.EVT_BUTTON, self.add, self.add_btn) self.Bind(wx.EVT_BUTTON, self.remove, self.remove_btn) self.Bind(wx.EVT_LISTBOX, self.update_selection_preview, self.selection_list_box) self.Bind(wx.EVT_BUTTON, self.move_up, self.up_btn) self.Bind(wx.EVT_BUTTON, self.move_down, self.down_btn) self.Bind(wx.EVT_LISTBOX_DCLICK, self.remove, self.selection_list_box) self.remove_btn.SetLabel("Remove") self.selection_list_box.Clear() self.selection_list_box.AppendItems(self.selection_choices) if not self.selection_list_box.IsEmpty(): self.selection_list_box.SetSelection(0) self.picker_list_box.Clear() self.picker_list_box.AppendItems(self.picker_choices) if not self.picker_list_box.IsEmpty(): self.picker_list_box.SetSelection(0) self.picker_axes = self.picker_figure.add_subplot(111) self.selection_axes = self.selection_figure.add_subplot(111) self.update_picker_preview() self.update_selection_preview() def __set_properties(self): # begin wxGlade: ColourPickerPanel.__set_properties self.move_panel.SetMinSize((170, -1)) self.picker_list_box.SetMinSize((170, 256)) self.picker_canvas.SetMinSize((64, 64)) self.selection_list_box.SetMinSize((170, 256)) self.up_btn.SetMinSize((80, -1)) self.down_btn.SetMinSize((80, -1)) self.selection_canvas.SetMinSize((64, 64)) self.main_panel.SetMinSize((450, -1)) # end wxGlade def __do_layout(self): # begin wxGlade: ColourPickerPanel.__do_layout parent_sizer = wx.BoxSizer(wx.HORIZONTAL) main_sizer = wx.BoxSizer(wx.VERTICAL) list_grid_sizer = wx.BoxSizer(wx.HORIZONTAL) sizer_4 = wx.BoxSizer(wx.VERTICAL) selection_preview_sizer = wx.BoxSizer(wx.HORIZONTAL) move_grid = wx.GridSizer(1, 2, 0, 5) sizer_3 = wx.BoxSizer(wx.VERTICAL) sizer_2 = wx.BoxSizer(wx.VERTICAL) picker_preview_sizer = wx.BoxSizer(wx.HORIZONTAL) grid_sizer = wx.GridSizer(1, 3, 10, 10) borders_label = wx.StaticText(self.main_panel, wx.ID_ANY, "Choose Styles: ") borders_label.SetMinSize((128, 20)) borders_label.SetFont( wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer.Add(borders_label, 0, wx.BOTTOM, 7) grid_sizer.Add(self.move_panel, 1, wx.ALIGN_CENTER | wx.EXPAND, 0) main_sizer.Add(grid_sizer, 0, 0, 0) sizer_2.Add(self.picker_list_box, 5, wx.EXPAND, 0) sizer_2.Add((0, 0), 0, 0, 0) preview_label = wx.StaticText(self.main_panel, wx.ID_ANY, "Preview: ") picker_preview_sizer.Add(preview_label, 0, 0, 0) picker_preview_sizer.Add(self.picker_canvas, 1, wx.EXPAND, 0) sizer_2.Add(picker_preview_sizer, 0, wx.EXPAND | wx.TOP, 5) list_grid_sizer.Add(sizer_2, 1, wx.EXPAND, 0) sizer_3.Add(self.add_btn, 0, wx.ALIGN_CENTER | wx.BOTTOM, 10) sizer_3.Add(self.remove_btn, 0, wx.ALIGN_CENTER, 0) list_grid_sizer.Add(sizer_3, 5, wx.ALIGN_CENTER | wx.BOTTOM, 64) sizer_4.Add(self.selection_list_box, 5, wx.EXPAND, 0) move_grid.Add(self.up_btn, 0, wx.ALIGN_CENTER, 0) move_grid.Add(self.down_btn, 0, wx.ALIGN_CENTER, 0) sizer_4.Add(move_grid, 0, wx.EXPAND, 0) preview_label_1 = wx.StaticText(self.main_panel, wx.ID_ANY, "Preview: ") selection_preview_sizer.Add(preview_label_1, 0, 0, 0) selection_preview_sizer.Add(self.selection_canvas, 1, wx.EXPAND, 0) sizer_4.Add(selection_preview_sizer, 0, wx.EXPAND | wx.TOP, 5) list_grid_sizer.Add(sizer_4, 1, wx.EXPAND, 0) main_sizer.Add(list_grid_sizer, 0, 0, 0) static_line_11 = wx.StaticLine(self.main_panel, wx.ID_ANY) main_sizer.Add(static_line_11, 0, wx.BOTTOM | wx.EXPAND | wx.TOP, 5) self.main_panel.SetSizer(main_sizer) parent_sizer.Add(self.main_panel, 0, wx.ALL, 10) self.SetSizer(parent_sizer) parent_sizer.Fit(self) self.Layout() # end wxGlade borders_label.SetLabel(self.label) def add(self, event): # wxGlade: ColourPickerPanel.<event_handler> """ Event handler for adding the colour currently selected in the 'picker' to the 'selection' """ selection = self.picker_list_box.GetSelection() if selection == -1: return selection_string = self.picker_list_box.GetString(selection) if selection_string == '': return self.selection_list_box.Append(selection_string) self.update_picker_preview() self.selection_list_box.SetSelection( self.selection_list_box.GetCount() - 1) self.update_selection_preview() event.Skip() def remove(self, event): # wxGlade: ColourPickerPanel.<event_handler> """ Event handler for removing the colour currently selected in the 'selection' """ selection = self.selection_list_box.GetSelection() if selection == -1: return selection_string = self.selection_list_box.GetString(selection) if selection_string == '': return self.selection_list_box.Delete(self.selection_list_box.GetSelection()) self.update_selection_preview() event.Skip() def update_preview(self, list_obj: wx.ListBox, axes: matplotlib.axes.Axes): """ Update the preview from the given list :param list_obj: The list to update the preview for :type list_obj: wx.ListBox :param axes: The preview axes to update :type axes: matplotlib.axes.Axes """ axes.clear() axes.axis('off') selection_string = list_obj.GetStringSelection() if selection_string == '': return axes.scatter(1, 1, s=400, color=selection_string, marker="s") def pick(self, *args): """ Open a wx.ColourDialog to edit the colour currently selected in the picker """ selection = self.selection_list_box.GetSelection() if selection == -1: return selection_string = self.selection_list_box.GetString(selection) if selection_string == '': return colour = wx.ColourData() colour.SetColour(selection_string) dlg = wx.ColourDialog(self, data=colour) res = dlg.ShowModal() if res == wx.ID_OK: self.selection_list_box.Delete(selection) self.selection_list_box.InsertItems( [dlg.GetColourData().GetColour().GetAsString(wx.C2S_HTML_SYNTAX)], selection) # yapf: disable self.selection_list_box.SetSelection(selection) self.update_selection_preview() dlg.Destroy() def GetSelection(self) -> List[str]: """ Returns a list of the currently selected colours :rtype: list of str """ return [ self.selection_list_box.GetString(item) for item in range(self.selection_list_box.GetCount()) ] get_selection = GetSelection
class PlotPanel(wx.Panel): """ The PlotPanel """ def __init__(self, parent, color=None, dpi=None, **kwargs): # initialize Panel if 'id' not in kwargs.keys(): kwargs['id'] = wx.ID_ANY if 'style' not in kwargs.keys(): kwargs['style'] = wx.NO_FULL_REPAINT_ON_RESIZE wx.Panel.__init__(self, parent, **kwargs) self.SetMinSize((100, 40)) # initialize matplotlib stuff self.figure = Figure(None, dpi=dpi, facecolor='white') self.canvas = FigureCanvas(self, -1, self.figure) self.canvas.SetMinSize((30, 10)) self.SetBackgroundColour('white') # Add the canvas to the sizer. self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.canvas, 1, wx.EXPAND) self.SetSizer(self.sizer) #self.canvas.mpl_connect('button_press_event', self.onClick) self.canvas.Bind(wx.EVT_SET_FOCUS, self.onSetFocus) self.Bind(wx.EVT_SET_FOCUS, self.onSetFocus2) self.canvas.Bind(wx.EVT_KEY_DOWN, self.onKeyDown) self.Bind(wx.EVT_KEY_DOWN, self.onKeyDown) self.canvas.Bind(wx.EVT_KEY_UP, self.onKeyUp) self.Bind(wx.EVT_KEY_UP, self.onKeyUp) self.Bind(wx.EVT_LEFT_DOWN, self.onLeftDown) self.canvas.Bind(wx.EVT_LEFT_DOWN, self.onLeftDown) def onClick(self, event): print "Clicked in View. event: %s" % event.guiEvent event.guiEvent.ResumePropagation(1) event.guiEvent.Skip() def onWxClick(self, event): print "Got the WX event." def onSetFocus(self, event): print "Canvas got Focus" event.Skip() def onSetFocus2(self, event): print "PlotPanel got Focus" def onKeyDown(self, event): print "Propagating keyDown in plotPanel" event.ResumePropagation(1) event.Skip() def onKeyUp(self, event): print "Propagating keyUp in plotPanel" event.ResumePropagation(1) event.Skip() def onLeftDown(self, event): print "PlotPanel LEFT DOWN" event.ResumePropagation(30) event.Skip() def SetColor(self, rgbtuple=None): """Set figure and canvas colours to be the same.""" if rgbtuple is None: rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get() clr = [c / 255. for c in rgbtuple] self.figure.set_facecolor(clr) self.figure.set_edgecolor(clr) self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple)) self.canvas.Refresh()
class plot1(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, size=(700, 500)) self.figure = plt.figure() self.canvas = FigureCanvas(self, -1, self.figure) self.canvas.SetMinSize(wx.Size(1, 1)) self.toolbar = NavigationToolbar(self.canvas) self.toolbar.Hide() self._init_plots() def _init_plots(self): self.ax_time = self.figure.add_subplot(111) self.ax_freq = self.figure.add_subplot(111) self.ax_hist = self.figure.add_subplot(111) self.ax_time.plot([], []) self.ax_time.set_title("Loaded Data") self.ax_time.set_xlabel("Time [s]") self.ax_time.set_ylabel("Amplitude [V]") def plot(self, data, time, type, rate=None): if (type == 1): self.ax_time.clear() self.ax_freq.clear() self.ax_hist.clear() y = data t = time self.ax_time.plot(t, y) #set the plot params self.ax_time.set_title("Time Series Measurement") self.ax_time.set_xlabel("Time [s]") self.ax_time.set_ylabel("Voltage [V]") #draw the plot self.canvas.draw() elif (type == 2): self.ax_time.clear() self.ax_freq.clear() self.ax_hist.clear() #freq domain N = len(data) T = 1 / rate xf = np.linspace(0.0, 1.0 / (2.0 * T), N // 2) # taking the fft fftData = np.abs(np.fft.rfft(data)) # determine the offset offset = len(fftData) - len(xf) if (offset < 0): # pad the data array with zeros for i in range(offset): fftData.append[0] elif (offset > 0): fftData = fftData[:-offset] # fftTime = np.fft.rfftfreq(self.chunksize, 1./self.samplerate) self.ax_freq.plot(xf, fftData) self.ax_freq.set_title("Signal FFT") self.ax_freq.set_xlabel("Frequency [Hz]") self.ax_freq.set_ylabel("Amplitude |P(f)|") self.canvas.draw() elif (type == 3): self.ax_time.clear() self.ax_freq.clear() self.ax_hist.clear() counts, bins, patches = self.ax_hist.hist(data, 30) self.ax_hist.set_title("Signal Histogram") self.ax_hist.set_xlabel("Voltage [V]") self.ax_hist.set_ylabel("Counts") self.canvas.draw() #hist '''''' def OnDelete(self): print(">>> closing plots") plt.close(self.figure)
class FIRBandpassConfigPanel(FilterConfigPanel): def __init__(self, *args, **kwargs): FilterConfigPanel.__init__(self, *args, **kwargs) # options go in top-level sizer self.initOptions() # other stuff split horizontally by bottomSizer self.bottomSizer = wx.BoxSizer(wx.HORIZONTAL) self.initSliders() self.initResponse() self.sizer.Add(self.bottomSizer, proportion=1, flag=wx.EXPAND) self.initLayout() def initOptions(self): optionsSizer = wx.BoxSizer(wx.HORIZONTAL) self.filtTypeComboBox = wx.ComboBox(self, choices=list(self.flt.filtMap.keys()), value=self.flt.filtType, style=wx.CB_DROPDOWN) self.Bind(wx.EVT_COMBOBOX, self.setFiltType, self.filtTypeComboBox) optionsSizer.Add(self.filtTypeComboBox, proportion=1, flag=wx.LEFT | wx.TOP | wx.RIGHT | wx.ALIGN_CENTER, border=20) self.sizer.Add(optionsSizer, proportion=0)#, flag=wx.EXPAND) def setFiltType(self, event): filtType = self.filtTypeComboBox.GetValue() if filtType not in self.flt.filtMap.keys(): raise RuntimeError('Invalid filter type: %s.' % str(filtType)) self.flt.filtType = filtType self.updateResponse() def initSliders(self): sliderSizer = wx.BoxSizer(wx.HORIZONTAL) lowFreqControlBox = widgets.ControlBox(self, label='lowFreq', orient=wx.VERTICAL) self.lowFreqText = wx.StaticText(self, label='%6.2f(Hz)' % self.flt.lowFreq) lowFreqTextSizer = wx.BoxSizer(orient=wx.VERTICAL) lowFreqTextSizer.Add(self.lowFreqText, proportion=0, flag=wx.ALIGN_CENTER_HORIZONTAL) self.lowFreqSlider = wx.Slider(self, style=wx.SL_VERTICAL | wx.SL_INVERSE, minValue=0, maxValue=int(self.flt.nyquist*4), value=int(self.flt.lowFreq*4)) self.Bind(wx.EVT_SLIDER, self.setLowFreq, self.lowFreqSlider) lowFreqControlBox.Add(lowFreqTextSizer, proportion=0, flag=wx.TOP | wx.BOTTOM | wx.EXPAND, border=8) lowFreqControlBox.Add(self.lowFreqSlider, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=25) sliderSizer.Add(lowFreqControlBox, proportion=1, flag=wx.ALL | wx.EXPAND, border=10) highFreqControlBox = widgets.ControlBox(self, label='highFreq', orient=wx.VERTICAL) self.highFreqText = wx.StaticText(self, label='%6.2f(Hz)' % self.flt.highFreq) highFreqTextSizer = wx.BoxSizer(orient=wx.VERTICAL) highFreqTextSizer.Add(self.highFreqText, proportion=0, flag=wx.ALIGN_CENTER_HORIZONTAL) self.highFreqSlider = wx.Slider(self, style=wx.SL_VERTICAL | wx.SL_INVERSE, minValue=0, maxValue=int(self.flt.nyquist*4), value=int(self.flt.highFreq*4)) self.Bind(wx.EVT_SLIDER, self.setHighFreq, self.highFreqSlider) highFreqControlBox.Add(highFreqTextSizer, proportion=0, flag=wx.TOP | wx.BOTTOM | wx.EXPAND, border=8) highFreqControlBox.Add(self.highFreqSlider, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=25) sliderSizer.Add(highFreqControlBox, proportion=1, flag=wx.ALL | wx.EXPAND, border=10) orderControlBox = widgets.ControlBox(self, label='Order', orient=wx.VERTICAL) self.orderText = wx.StaticText(self, label='%2d' % self.flt.order) orderTextSizer = wx.BoxSizer(orient=wx.VERTICAL) orderTextSizer.Add(self.orderText, proportion=0, flag=wx.ALIGN_CENTER_HORIZONTAL) self.orderSlider = wx.Slider(self, style=wx.SL_VERTICAL | wx.SL_INVERSE, minValue=2, maxValue=50, value=self.flt.order // 2) self.Bind(wx.EVT_SLIDER, self.setOrder, self.orderSlider) orderControlBox.Add(orderTextSizer, proportion=0, flag=wx.TOP | wx.BOTTOM | wx.EXPAND, border=8) orderControlBox.Add(self.orderSlider, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=25) sliderSizer.Add(orderControlBox, proportion=1, flag=wx.ALL | wx.EXPAND, border=10) self.bottomSizer.Add(sliderSizer, proportion=1, flag=wx.EXPAND) def setLowFreq(self, event): self.flt.lowFreq = self.lowFreqSlider.GetValue() / 4.0 self.lowFreqText.SetLabel('%6.2f(Hz)' % self.flt.lowFreq) self.updateResponse() def setHighFreq(self, event): self.flt.highFreq = self.highFreqSlider.GetValue() / 4.0 self.highFreqText.SetLabel('%6.2f(Hz)' % self.flt.highFreq) self.updateResponse() def setOrder(self, event): self.flt.order = self.orderSlider.GetValue() * 2 self.orderText.SetLabel('%2d' % self.flt.order) self.updateResponse() def initResponse(self): self.freqResponseFig = plt.Figure() self.freqResponseCanvas = FigureCanvas(parent=self, id=wx.ID_ANY, figure=self.freqResponseFig) self.freqResponseAx = self.freqResponseFig.add_subplot(1,1,1) #self.freqResponseFig.tight_layout() self.phaseResponseFig = plt.Figure() self.phaseResponseCanvas = FigureCanvas(parent=self, id=wx.ID_ANY, figure=self.phaseResponseFig) self.phaseResponseAx = self.phaseResponseFig.add_subplot(1,1,1) #self.freqResponseFig.tight_layout() responseSizer = wx.BoxSizer(wx.VERTICAL) freqResponseControlBox = widgets.ControlBox(self, label='Frequency Response', orient=wx.VERTICAL) freqResponseControlBox.Add(self.freqResponseCanvas, proportion=1, flag=wx.ALL | wx.EXPAND, border=8) responseSizer.Add(freqResponseControlBox, proportion=1, flag=wx.TOP | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=10) phaseResponseControlBox = widgets.ControlBox(self, label='Phase Response', orient=wx.VERTICAL) phaseResponseControlBox.Add(self.phaseResponseCanvas, proportion=1, flag=wx.ALL | wx.EXPAND, border=8) responseSizer.Add(phaseResponseControlBox, proportion=1, flag=wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=10) self.bottomSizer.Add(responseSizer, proportion=1, flag=wx.EXPAND) self.freqResponseCanvas.SetMinSize((0,0)) self.phaseResponseCanvas.SetMinSize((0,0)) # could we prevent resize when panel is not visible? XXX - idfah self.freqResponseLastSize = (0,0) self.freqResponseCanvas.Bind(wx.EVT_SIZE, self.freqResponseResize) self.phaseResponseLastSize = (0,0) self.phaseResponseCanvas.Bind(wx.EVT_SIZE, self.phaseResponseResize) self.updateResponse() def freqResponseResize(self, event): # prevents handling extra resize events, hack XXX - idfah size = self.freqResponseCanvas.GetSize() if self.freqResponseLastSize == size: return self.freqResponseLastSize = size event.Skip() def phaseResponseResize(self, event): # prevents handling extra resize events, hack XXX - idfah size = self.phaseResponseCanvas.GetSize() if self.phaseResponseLastSize == size: return self.phaseResponseLastSize = size event.Skip() def updateResponse(self): self.flt.updateFilter() self.freqResponseAx.cla() self.flt.bp.plotFreqResponse(ax=self.freqResponseAx, linewidth=2) self.freqResponseAx.autoscale(tight=True) self.freqResponseAx.legend(prop={'size': 12}) self.freqResponseCanvas.draw() self.phaseResponseAx.cla() self.flt.bp.plotPhaseResponse(ax=self.phaseResponseAx, linewidth=2) self.phaseResponseAx.legend(prop={'size': 12}) self.phaseResponseAx.autoscale(tight=True) self.phaseResponseCanvas.draw()
class StylePickerPanel(wx.Panel): def __init__( self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.TAB_TRAVERSAL, name=wx.PanelNameStr, label="Choose Styles: ", selection_choices=None, ): if selection_choices is None: selection_choices = default_styles[:] self.label = label self.selection_choices = selection_choices args = (parent, id, pos, size) kwds = {"style": style, "name": name} # begin wxGlade: StylePickerPanel.__init__ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL wx.Panel.__init__(self, *args, **kwds) self.main_panel = wx.Panel(self, wx.ID_ANY) self.move_panel = wx.Panel(self.main_panel, wx.ID_ANY) self.picker_list_box = wx.ListBox(self.main_panel, wx.ID_ANY, choices=[]) self.picker_figure = Figure() self.picker_canvas = FigureCanvas(self.main_panel, wx.ID_ANY, self.picker_figure) self.add_btn = wx.Button(self.main_panel, wx.ID_ANY, u"Add 🡲") self.remove_btn = wx.Button(self.main_panel, wx.ID_ANY, u"🡰 Remove") self.selection_list_box = wx.ListBox(self.main_panel, wx.ID_ANY, choices=[]) self.up_btn = wx.Button(self.main_panel, wx.ID_ANY, u"🡱 Up") self.down_btn = wx.Button(self.main_panel, wx.ID_ANY, u"🡳 Down") self.selection_figure = Figure() self.selection_canvas = FigureCanvas(self.main_panel, wx.ID_ANY, self.selection_figure) self.__set_properties() self.__do_layout() self.Bind(wx.EVT_LISTBOX, self.update_picker_preview, self.picker_list_box) self.Bind(wx.EVT_LISTBOX_DCLICK, self.add, self.picker_list_box) self.Bind(wx.EVT_BUTTON, self.add, self.add_btn) self.Bind(wx.EVT_BUTTON, self.remove, self.remove_btn) self.Bind(wx.EVT_LISTBOX, self.update_selection_preview, self.selection_list_box) self.Bind(wx.EVT_BUTTON, self.move_up, self.up_btn) self.Bind(wx.EVT_BUTTON, self.move_down, self.down_btn) # end wxGlade self.Bind(wx.EVT_LISTBOX_DCLICK, self.remove, self.selection_list_box) self.markers = { "point": ".", "pixel": ",", "circle": "o", "triangle_down": "v", "triangle_up": "^", "triangle_left": "<", "triangle_right": ">", "tri_down": "1", "tri_up": "2", "tri_left": "3", "tri_right": "4", "octagon": "8", "square": "s", "pentagon": "p", "plus (filled)": "P", "star": "*", "hexagon1": "h", "hexagon2": "H", "plus": "+", "x": "x", "x (filled)": "X", "diamond": "D", "thin_diamond": "d", "caretleft": 4, # (CARETLEFT), "caretright": 5, # (CARETRIGHT), "caretup": 6, # (CARETUP), "caretdown": 7, # (CARETDOWN), } for marker in self.selection_choices: for key in self.markers.keys(): if marker == self.markers[key]: self.selection_list_box.Append(key) if not self.selection_list_box.IsEmpty(): self.selection_list_box.SetSelection(0) for marker in list( filter(lambda x: x not in self.selection_choices, [self.markers[y] for y in self.markers])): for key in self.markers.keys(): if marker == self.markers[key]: self.picker_list_box.Append(key) self.picker_list_box.SetSelection(0) self.picker_axes = self.picker_figure.add_subplot(111) self.selection_axes = self.selection_figure.add_subplot(111) self.update_picker_preview() self.update_selection_preview() def __set_properties(self): # begin wxGlade: StylePickerPanel.__set_properties self.move_panel.SetMinSize((170, -1)) self.picker_list_box.SetMinSize((170, 256)) self.picker_canvas.SetMinSize((64, 64)) self.selection_list_box.SetMinSize((170, 256)) self.up_btn.SetMinSize((80, -1)) self.down_btn.SetMinSize((80, -1)) self.selection_canvas.SetMinSize((64, 64)) self.main_panel.SetMinSize((450, -1)) # end wxGlade def __do_layout(self): # begin wxGlade: StylePickerPanel.__do_layout parent_sizer = wx.BoxSizer(wx.HORIZONTAL) main_sizer = wx.BoxSizer(wx.VERTICAL) list_grid_sizer = wx.BoxSizer(wx.HORIZONTAL) sizer_4 = wx.BoxSizer(wx.VERTICAL) selection_preview_sizer = wx.BoxSizer(wx.HORIZONTAL) move_grid = wx.GridSizer(1, 2, 0, 5) sizer_3 = wx.BoxSizer(wx.VERTICAL) sizer_2 = wx.BoxSizer(wx.VERTICAL) picker_preview_sizer = wx.BoxSizer(wx.HORIZONTAL) grid_sizer = wx.GridSizer(1, 3, 10, 10) borders_label = wx.StaticText(self.main_panel, wx.ID_ANY, "Choose Styles: ") borders_label.SetMinSize((128, 20)) borders_label.SetFont( wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer.Add(borders_label, 0, wx.BOTTOM, 7) grid_sizer.Add(self.move_panel, 1, wx.ALIGN_CENTER | wx.EXPAND, 0) main_sizer.Add(grid_sizer, 0, 0, 0) sizer_2.Add(self.picker_list_box, 5, wx.EXPAND, 0) sizer_2.Add((0, 0), 0, 0, 0) preview_label = wx.StaticText(self.main_panel, wx.ID_ANY, "Preview: ") picker_preview_sizer.Add(preview_label, 0, 0, 0) picker_preview_sizer.Add(self.picker_canvas, 1, wx.EXPAND, 0) sizer_2.Add(picker_preview_sizer, 0, wx.EXPAND | wx.TOP, 5) list_grid_sizer.Add(sizer_2, 1, wx.EXPAND, 0) sizer_3.Add(self.add_btn, 0, wx.ALIGN_CENTER | wx.BOTTOM, 10) sizer_3.Add(self.remove_btn, 0, wx.ALIGN_CENTER, 0) list_grid_sizer.Add(sizer_3, 5, wx.ALIGN_CENTER | wx.BOTTOM, 64) sizer_4.Add(self.selection_list_box, 5, wx.EXPAND, 0) move_grid.Add(self.up_btn, 0, wx.ALIGN_CENTER, 0) move_grid.Add(self.down_btn, 0, wx.ALIGN_CENTER, 0) sizer_4.Add(move_grid, 0, wx.EXPAND, 0) preview_label_1 = wx.StaticText(self.main_panel, wx.ID_ANY, "Preview: ") selection_preview_sizer.Add(preview_label_1, 0, 0, 0) selection_preview_sizer.Add(self.selection_canvas, 1, wx.EXPAND, 0) sizer_4.Add(selection_preview_sizer, 0, wx.EXPAND | wx.TOP, 5) list_grid_sizer.Add(sizer_4, 1, wx.EXPAND, 0) main_sizer.Add(list_grid_sizer, 0, 0, 0) static_line_11 = wx.StaticLine(self.main_panel, wx.ID_ANY) main_sizer.Add(static_line_11, 0, wx.BOTTOM | wx.EXPAND | wx.TOP, 5) self.main_panel.SetSizer(main_sizer) parent_sizer.Add(self.main_panel, 0, wx.ALL, 10) self.SetSizer(parent_sizer) parent_sizer.Fit(self) self.Layout() # end wxGlade borders_label.SetLabel(self.label) def move_up(self, event): # wxGlade: StylePickerPanel.<event_handler> self.move(-1) event.Skip() def move_down(self, event): # wxGlade: StylePickerPanel.<event_handler> self.move(1) event.Skip() def move(self, direction=1): selection = self.selection_list_box.GetSelection() selection_string = self.selection_list_box.GetString(selection) if self.selection_list_box.GetCount( ) == selection + direction or selection + direction < 0: return self.selection_list_box.Delete(selection) self.selection_list_box.InsertItems([selection_string], selection + direction) self.selection_list_box.SetSelection(selection + direction) def add(self, event): # wxGlade: StylePickerPanel.<event_handler> selection = self.picker_list_box.GetSelection() if selection == -1: return selection_string = self.picker_list_box.GetString(selection) if selection_string == '': return self.selection_list_box.Append(selection_string) self.picker_list_box.Delete(selection) self.update_picker_preview() event.Skip() def remove(self, event): # wxGlade: StylePickerPanel.<event_handler> selection = self.selection_list_box.GetSelection() if selection == -1: return selection_string = self.selection_list_box.GetString(selection) if selection_string == '': return self.picker_list_box.Append(selection_string) self.selection_list_box.Delete(self.selection_list_box.GetSelection()) self.update_selection_preview() event.Skip() def update_picker_preview(self, *_): # wxGlade: StylePickerPanel.<event_handler> self.update_preview(self.picker_list_box, self.picker_axes) self.picker_canvas.draw_idle() def update_selection_preview( self, *_): # wxGlade: StylePickerPanel.<event_handler> self.update_preview(self.selection_list_box, self.selection_axes) self.selection_canvas.draw_idle() def update_preview(self, list_obj, axes): axes.clear() axes.axis('off') selection_string = list_obj.GetStringSelection() if selection_string == '': return axes.scatter(1, 1, s=200, color="red", marker=self.markers[selection_string]) do_layout = __do_layout set_properties = __set_properties def get_selection(self): return [ self.markers[self.selection_list_box.GetString(item)] for item in range(self.selection_list_box.GetCount()) ] def _do_layout(self): return self.__do_layout() def _set_properties(self): return self.__set_properties()
class HistoryTab(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, wx.ID_ANY) self.parent = parent self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER) # PLOT Panel --------------------------------------------------------------------------------------------------- self.figure = plt.figure(figsize=(1, 1)) # look into Figure((5, 4), 75) self.canvas = FigureCanvas(self.plot_panel, -1, self.figure) self.toolbar = NavigationToolbar(self.canvas) self.toolbar.Realize() self.ax1 = self.figure.add_subplot(211) self.ax2 = self.figure.add_subplot(212) self.temporal, = self.ax1.plot([], [], linestyle='-') self.spectral, = self.ax2.plot([], [], color='#C02942') # Open history dialog ------------------------------------------------------------------------------------------ self.btn_openHistory = wx.Button(self, wx.ID_ANY, "Open History") self.Bind(wx.EVT_BUTTON, self.OnOpen, self.btn_openHistory) self.text_ctrl_fs = wx.TextCtrl(self, wx.ID_ANY, "") self.text_ctrl_samples = wx.TextCtrl(self, wx.ID_ANY, "") self.text_ctrl_rms = wx.TextCtrl(self, wx.ID_ANY, "") self.text_ctrl_thdn = wx.TextCtrl(self, wx.ID_ANY, "") self.text_ctrl_thd = wx.TextCtrl(self, wx.ID_ANY, "") self.__set_properties() self.__do_layout() self.__do_plot_layout() def __set_properties(self): self.SetBackgroundColour(wx.Colour(240, 240, 240)) self.canvas.SetMinSize((700, 490)) def __do_layout(self): sizer_2 = wx.GridSizer(1, 1, 0, 0) grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0) grid_sizer_plot = wx.GridBagSizer(0, 0) grid_sizer_report = wx.GridBagSizer(0, 0) # LEFT PANEL --------------------------------------------------------------------------------------------------- lbl_fs = wx.StaticText(self, wx.ID_ANY, "Fs:") lbl_samples = wx.StaticText(self, wx.ID_ANY, "Samples:") lbl_rms = wx.StaticText(self, wx.ID_ANY, "RMS:") lbl_thdn = wx.StaticText(self, wx.ID_ANY, "THD+N:") lbl_THD = wx.StaticText(self, wx.ID_ANY, "THD:") static_line_1 = wx.StaticLine(self, wx.ID_ANY) static_line_1.SetMinSize((180, 2)) static_line_2 = wx.StaticLine(self, wx.ID_ANY) static_line_2.SetMinSize((180, 2)) grid_sizer_report.Add(self.btn_openHistory, (0, 0), (1, 2), wx.LEFT | wx.TOP, 5) grid_sizer_report.Add(static_line_2, (1, 0), (1, 2), wx.ALL, 5) grid_sizer_report.Add(lbl_fs, (2, 0), (1, 1), wx.LEFT | wx.RIGHT, 5) grid_sizer_report.Add(self.text_ctrl_fs, (2, 1), (1, 1), wx.BOTTOM, 5) grid_sizer_report.Add(lbl_samples, (3, 0), (1, 1), wx.LEFT | wx.RIGHT, 5) grid_sizer_report.Add(self.text_ctrl_samples, (3, 1), (1, 1), wx.BOTTOM, 5) grid_sizer_report.Add(static_line_1, (4, 0), (1, 2), wx.ALL, 5) grid_sizer_report.Add(lbl_rms, (5, 0), (1, 1), wx.LEFT | wx.RIGHT, 5) grid_sizer_report.Add(self.text_ctrl_rms, (5, 1), (1, 1), wx.BOTTOM, 5) grid_sizer_report.Add(lbl_thdn, (6, 0), (1, 1), wx.LEFT | wx.RIGHT, 5) grid_sizer_report.Add(self.text_ctrl_thdn, (6, 1), (1, 1), wx.BOTTOM, 5) grid_sizer_report.Add(lbl_THD, (7, 0), (1, 1), wx.LEFT | wx.RIGHT, 5) grid_sizer_report.Add(self.text_ctrl_thd, (7, 1), (1, 1), 0, 0) grid_sizer_1.Add(grid_sizer_report, 1, wx.EXPAND, 0) # PLOT PANEL --------------------------------------------------------------------------------------------------- grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.AddGrowableRow(0) grid_sizer_plot.AddGrowableCol(0) self.plot_panel.SetSizer(grid_sizer_plot) grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5) grid_sizer_1.AddGrowableRow(0) grid_sizer_1.AddGrowableCol(1) sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0) self.SetSizer(sizer_2) self.Layout() def error_dialog(self, error_message): print(error_message) dial = wx.MessageDialog(None, str(error_message), 'Error', wx.OK | wx.ICON_ERROR) dial.ShowModal() def OnOpen(self, event): Path("results/history").mkdir(parents=True, exist_ok=True) with wx.FileDialog(self, "Open previous measurement:", wildcard="CSV files (*.csv)|*.csv", defaultDir="results/history", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog: if fileDialog.ShowModal() == wx.ID_CANCEL: return # the user changed their mind # Proceed loading the file chosen by the user pathname = fileDialog.GetPath() try: with open(pathname, 'r') as file: self.open_history(file) except (IOError, ValueError) as e: wx.LogError("Cannot open file '%s'." % pathname) self.error_dialog(e) def open_history(self, file): df = pd.read_csv(file) try: xt = df['xt'].to_numpy() yt = df['yt'].to_numpy() xf = df['xf'].to_numpy() # https://stackoverflow.com/a/18919965/3382269 # https://stackoverflow.com/a/51725795/3382269 df['yf'] = df['yf'].str.replace('i', 'j').apply(lambda x: np.complex(x)) yf = df['yf'].to_numpy() except KeyError: raise ValueError( 'Incorrect file attempted to be opened. ' '\nCheck data headers. xt, yt, xf, yf should be present') self.process_raw_input(xt, yt, xf, yf) def process_raw_input(self, xt, yt, xf, yf): yrms = np.sqrt(np.mean(np.abs(yt)**2)) N = len(xt) Fs = round(1 / (xt[1] - xt[0]), 2) # SPECTRAL ----------------------------------------------------------------------------------------------------- if (N % 2) == 0: # for even values of N: length is (N / 2) + 1 fft_length = int(N / 2) + 1 else: # for odd values of N: length is (N + 1) / 2 fft_length = int((N + 2) / 2) xf_rfft, yf_rfft = xf[:fft_length], yf[:fft_length] thdn, *_ = THDN_F(yf_rfft, Fs, N) thd = THD(yf_rfft, Fs) self.plot(xt, yt, fft_length, xf_rfft, yf_rfft) self.results_update(Fs, N, yrms, thdn, thd) # ------------------------------------------------------------------------------------------------------------------ def __do_plot_layout(self): self.ax1.set_title('SAMPLED TIMED SERIES DATA') self.ax1.set_xlabel('TIME (ms)') self.ax1.set_ylabel('AMPLITUDE') self.ax2.set_title('DIGITIZED WAVEFORM SPECTRAL RESPONSE') self.ax2.set_xlabel('FREQUENCY (kHz)') self.ax2.set_ylabel('MAGNITUDE (dB)') self.ax2.grid() self.figure.align_ylabels([self.ax1, self.ax2]) self.figure.tight_layout() def plot(self, xt, yt, fft_length, xf, yf): # TEMPORAL ----------------------------------------------------------------------------------------------------- self.temporal.set_data(xt, yt) try: self.ax1.relim() # recompute the ax.dataLim except ValueError: print( f'Are the lengths of xt: {len(xt)} and yt: {len(yt)} mismatched?' ) raise self.ax1.margins(x=0) self.ax1.autoscale() # SPECTRAL ----------------------------------------------------------------------------------------------------- yf_peak = max(abs(yf)) self.spectral.set_data(xf / 1000, 20 * np.log10(np.abs(yf / yf_peak))) try: self.ax2.relim() # recompute the ax.dataLim except ValueError: print( f'Are the lengths of xt: {len(xf)} and yt: {len(yf)} mismatched?' ) raise self.ax2.autoscale() xf_left = 0 xf_right = xf[fft_length - 1] self.ax2.set_xlim(left=xf_left / 1000, right=xf_right / 1000) # UPDATE PLOT FEATURES ----------------------------------------------------------------------------------------- self.figure.tight_layout() self.toolbar.update() # Not sure why this is needed - ADS self.canvas.draw() self.canvas.flush_events() def results_update(self, Fs, N, yrms, thdn, thd): self.text_ctrl_fs.SetLabelText(str(Fs)) self.text_ctrl_samples.SetLabelText(str(N)) self.text_ctrl_rms.SetValue(f"{'{:0.3e}'.format(yrms)}") self.text_ctrl_thdn.SetValue( f"{round(thdn * 100, 3)}% or {round(np.log10(thdn), 1)}dB") self.text_ctrl_thd.SetValue( f"{round(thd * 100, 3)}% or {round(np.log10(thd), 1)}dB")
class CTRL_Graphique(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, -1, style=wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER) self.dictParametres = None self.afficher_valeurs = False # Init canvas self.figure = matplotlib.pyplot.figure() self.canvas = Canvas(self, -1, self.figure) self.canvas.SetMinSize((20, 20)) self.SetColor((255, 255, 255)) # Layout sizer_canvas = wx.BoxSizer(wx.VERTICAL) sizer_canvas.Add(self.canvas, 1, wx.EXPAND, 0) self.SetSizer(sizer_canvas) self.Layout() def OnBoutonZoom(self, event): import DLG_Zoom_graphe dlg = DLG_Zoom_graphe.Dialog(self, figure=self.figure) dlg.ShowModal() dlg.Destroy() def OnBoutonOptions(self, event): # Création du menu contextuel menuPop = UTILS_Adaptations.Menu() item = wx.MenuItem(menuPop, 10, _(u"Afficher les valeurs"), _(u"Afficher les valeurs"), wx.ITEM_CHECK) menuPop.AppendItem(item) self.Bind(wx.EVT_MENU, self.On_afficher_valeurs, id=10) if self.afficher_valeurs == True: item.Check(True) self.PopupMenu(menuPop) menuPop.Destroy() def On_afficher_valeurs(self, event): self.afficher_valeurs = not self.afficher_valeurs self.MAJ() def SetColor(self, rgbtuple=None): """Set figure and canvas colours to be the same.""" if rgbtuple is None: rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get() clr = [c / 255. for c in rgbtuple] self.figure.set_facecolor(clr) self.figure.set_edgecolor(clr) self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple)) def ConvertCouleur(self, couleur=None): return [c / 255. for c in couleur] def SetParametres(self, dictParametres=None): self.dictParametres = dictParametres def MAJ(self): self.figure.clear() if self.dictParametres == None: wx.CallAfter(self.SendSizeEvent) return if self.dictParametres[ "IDmodele"] == "repartition_categories_debit_tresorerie": self.Graphe_repartition_categories(typeCategorie="debit", typeDonnees="tresorerie") if self.dictParametres[ "IDmodele"] == "repartition_categories_credit_tresorerie": self.Graphe_repartition_categories(typeCategorie="credit", typeDonnees="tresorerie") if self.dictParametres[ "IDmodele"] == "repartition_categories_debit_budgetaires": self.Graphe_repartition_categories(typeCategorie="debit", typeDonnees="budgetaires") if self.dictParametres[ "IDmodele"] == "repartition_categories_credit_budgetaires": self.Graphe_repartition_categories(typeCategorie="credit", typeDonnees="budgetaires") if self.dictParametres[ "IDmodele"] == "repartition_categories_debit_tresorerie_budgetaires": self.Graphe_repartition_categories( typeCategorie="debit", typeDonnees="tresorerie+budgetaires") if self.dictParametres[ "IDmodele"] == "repartition_categories_credit_tresorerie_budgetaires": self.Graphe_repartition_categories( typeCategorie="credit", typeDonnees="tresorerie+budgetaires") if self.dictParametres["IDmodele"] == "tiers_debit": self.Graphe_tiers(typeCategorie="debit") if self.dictParametres["IDmodele"] == "tiers_credit": self.Graphe_tiers(typeCategorie="credit") self.Layout() def Graphe_repartition_categories(self, typeCategorie="", typeDonnees="tresorerie+budgetaires"): # Récupération des données conditions = [] if self.dictParametres["date_debut"] != None: conditions.append("date_budget>='%s'" % self.dictParametres["date_debut"]) conditions.append("date_budget<='%s'" % self.dictParametres["date_fin"]) if self.dictParametres["IDanalytique"] != None: conditions.append("IDanalytique=%d" % self.dictParametres["IDanalytique"]) if len(conditions) > 0: ConditionsStr = "AND " + " AND ".join(conditions) else: ConditionsStr = "" DB = GestionDB.DB() dictDonnees = {} if "budgetaires" in typeDonnees: req = """SELECT compta_operations_budgetaires.IDcategorie, compta_categories.nom, SUM(compta_operations_budgetaires.montant) FROM compta_operations_budgetaires LEFT JOIN compta_categories ON compta_categories.IDcategorie = compta_operations_budgetaires.IDcategorie WHERE compta_operations_budgetaires.type='%s' %s GROUP BY compta_operations_budgetaires.IDcategorie ;""" % (typeCategorie, ConditionsStr) DB.ExecuterReq(req) listeDonnees = DB.ResultatReq() for IDcategorie, nom, montant in listeDonnees: if dictDonnees.has_key(IDcategorie) == False: dictDonnees[IDcategorie] = {"nom": nom, "montant": 0.0} dictDonnees[IDcategorie]["montant"] += montant if "tresorerie" in typeDonnees: req = """SELECT compta_ventilation.IDcategorie, compta_categories.nom, SUM(compta_ventilation.montant) FROM compta_ventilation LEFT JOIN compta_categories ON compta_categories.IDcategorie = compta_ventilation.IDcategorie WHERE type='%s' %s GROUP BY compta_ventilation.IDcategorie ;""" % (typeCategorie, ConditionsStr) DB.ExecuterReq(req) listeDonnees = DB.ResultatReq() for IDcategorie, nom, montant in listeDonnees: if dictDonnees.has_key(IDcategorie) == False: dictDonnees[IDcategorie] = {"nom": nom, "montant": 0.0} dictDonnees[IDcategorie]["montant"] += montant DB.Close() if len(dictDonnees) == 0: return listeValeurs = [] listeLabels = [] listeCouleurs = [] montantTotal = 0.0 for IDcategorie, dictTemp in dictDonnees.iteritems(): montantTotal += dictTemp["montant"] index = 1 for IDcategorie, dictTemp in dictDonnees.iteritems(): listeValeurs.append(dictTemp["montant"]) label = dictTemp["nom"] if self.afficher_valeurs == True: label += u"\n%.2f %s" % (float(montant), SYMBOLE) listeLabels.append(label) couleur = 1.0 * montant / montantTotal couleur = matplotlib.cm.hsv(index * 0.1) listeCouleurs.append(couleur) index += 1 # Création du graphique ax = self.figure.add_subplot(111) cam = ax.pie(listeValeurs, labels=listeLabels, colors=listeCouleurs, autopct='%1.1f%%', shadow=False) title = ax.set_title( self.dictParametres["nom"], weight="bold", horizontalalignment='center') #, position=(0.5, 0.97)) matplotlib.pyplot.setp(title, rotation=0, fontsize=11) ax.set_aspect(1) labels, labelsPourcent = cam[1], cam[2] matplotlib.pyplot.setp(labels, rotation=0, fontsize=11) matplotlib.pyplot.setp(labelsPourcent, rotation=0, fontsize=9) # Finalisation ax.autoscale_view('tight') ax.figure.canvas.draw() wx.CallAfter(self.SendSizeEvent) def Graphe_tiers(self, typeCategorie=""): # Récupération des données conditions = [] if self.dictParametres["date_debut"] != None: conditions.append("date_budget>='%s'" % self.dictParametres["date_debut"]) conditions.append("date_budget<='%s'" % self.dictParametres["date_fin"]) if self.dictParametres["IDanalytique"] != None: conditions.append("IDanalytique=%d" % self.dictParametres["IDanalytique"]) if len(conditions) > 0: ConditionsStr = "AND " + " AND ".join(conditions) else: ConditionsStr = "" DB = GestionDB.DB() req = """SELECT compta_tiers.IDtiers, compta_tiers.nom, SUM(compta_ventilation.montant) FROM compta_tiers LEFT JOIN compta_operations ON compta_operations.IDtiers = compta_tiers.IDtiers LEFT JOIN compta_ventilation ON compta_ventilation.IDoperation = compta_operations.IDoperation WHERE type='%s' %s GROUP BY compta_tiers.IDtiers ;""" % (typeCategorie, ConditionsStr) DB.ExecuterReq(req) listeDonnees = DB.ResultatReq() DB.Close() if len(listeDonnees) == 0: return listeValeurs = [] listeLabels = [] listeCouleurs = [] for IDtiers, nom, montant in listeDonnees: listeValeurs.append(montant) listeLabels.append(nom) listeIndex = np.arange(len(listeLabels)) bar_height = 0.2 opacity = 0.4 ax = self.figure.add_subplot(111) barres = ax.barh(listeIndex, listeValeurs, height=bar_height, align='center', alpha=opacity) # Formatage des montants sur x majorFormatter = FormatStrFormatter(u"%d " + SYMBOLE) ax.xaxis.set_major_formatter(majorFormatter) # Affichage des labels x ax.set_yticks(listeIndex) ax.set_yticklabels(listeLabels) def autolabel(rects): # attach some text labels for rect in rects: width = rect.get_width() ax.text(width + 10, rect.get_y() + rect.get_height() / 2., u"%.2f %s" % (int(width), SYMBOLE), ha='left', va='center', fontsize=8, color="grey") if self.afficher_valeurs == True: autolabel(barres) # Recherche la largeur de texte max largeurMax = 0 for label in listeLabels: if len(label) > largeurMax: largeurMax = len(label) # Espaces autour du graph margeGauche = 0.1 + largeurMax * 0.008 self.figure.subplots_adjust(left=margeGauche, right=None, wspace=None, hspace=None) # Finalisation ax.autoscale_view('tight') ## ax.grid(True) ax.figure.canvas.draw() wx.CallAfter(self.SendSizeEvent) return # Récupération des données from Ol import OL_Suivi_budget analyse = OL_Suivi_budget.Analyse(self.dictBudget) listeCategories = analyse.GetValeurs() listeRealise = [] listeBudgete = [] listeLabels = [] for dictCategorie in listeCategories: listeRealise.append(dictCategorie["realise"]) listeBudgete.append(dictCategorie["plafond"]) listeLabels.append(dictCategorie["nomCategorie"]) ## if dictCategorie["typeCategorie"] == "debit" : ## solde = plafond - realise ## else : ## solde = realise - plafond ## # TEST ## listeIndex = np.arange(len(listeLabels)) ## bar_width = 0.2 ## opacity = 0.4 ## ## ax = self.figure.add_subplot(111) ## barres = ax.bar(listeIndex, listeRealise, width=bar_width, alpha=opacity, color="g", label=_(u"Réel")) ## barres = ax.bar(listeIndex + bar_width, listeBudgete, width=bar_width, alpha=opacity, color="b", label=_(u"Budgété")) ## ## # Formatage des montants sur y ## majorFormatter = FormatStrFormatter(SYMBOLE + u" %d") ## ax.yaxis.set_major_formatter(majorFormatter) ## ## # Affichage des labels x ## ax.set_xticks(listeIndex + bar_width) ## ax.set_xticklabels(listeLabels) ## ## labels = ax.get_xticklabels() ## setp(labels, rotation=45) ## ## # Légende ## props = matplotlib.font_manager.FontProperties(size=10) ## leg = ax.legend(loc='best', shadow=False, fancybox=True, prop=props) ## leg.get_frame().set_alpha(0.5) ## ## # Espaces autour du graph ## self.figure.subplots_adjust(left=0.12, bottom=0.40, right=None, wspace=None, hspace=None) # TEST listeIndex = np.arange(len(listeLabels)) bar_height = 0.2 opacity = 0.4 ax = self.figure.add_subplot(111) barresRealise = ax.barh(listeIndex, listeRealise, height=bar_height, alpha=opacity, color="g", label=_(u"Réel")) barresBudgete = ax.barh(listeIndex + bar_height, listeBudgete, height=bar_height, alpha=opacity, color="b", label=_(u"Budgété")) # Formatage des montants sur x majorFormatter = FormatStrFormatter(u"%d " + SYMBOLE) ax.xaxis.set_major_formatter(majorFormatter) # Affichage des labels x ax.set_yticks(listeIndex + bar_height) ax.set_yticklabels(listeLabels) def autolabel(rects): # attach some text labels for rect in rects: width = rect.get_width() ax.text(width + 20, rect.get_y() + rect.get_height() / 2., u"%.2f %s" % (int(width), SYMBOLE), ha='left', va='center', fontsize=8, color="grey") if self.afficher_valeurs == True: autolabel(barresRealise) autolabel(barresBudgete) # Recherche la largeur de texte max largeurMax = 0 for label in listeLabels: if len(label) > largeurMax: largeurMax = len(label) # Espaces autour du graph margeGauche = 0.1 + largeurMax * 0.008 self.figure.subplots_adjust(left=margeGauche, right=None, wspace=None, hspace=None) # Légende props = matplotlib.font_manager.FontProperties(size=10) leg = ax.legend(loc='best', shadow=False, fancybox=True, prop=props) leg.get_frame().set_alpha(0.5) # Finalisation ax.autoscale_view('tight') ax.grid(True) ax.figure.canvas.draw() wx.CallAfter(self.SendSizeEvent) return
class MatplotPanel(wx.Panel): clsFrame = None clsID_new_figure = wx.NOT_FOUND isInitialized = False kwargs = {} clsID_delete_datatip = wx.NewId() clsID_clear_datatip = wx.NewId() def __init__(self, parent, title=None, num=-1, thisFig=None): # set the size to positive value, otherwise the toolbar will assert # wxpython/ext/wxWidgets/src/gtk/bitmap.cpp(539): assert ""width > 0 && # height > 0"" failed in Create(): invalid bitmap size wx.Panel.__init__(self, parent, size=(100, 100)) # initialize matplotlib stuff self.figure = thisFig if not self.figure: self.figure = Figure(None, None) self.canvas = FigureCanvas(self, -1, self.figure) # since matplotlib 3.2, it does not allow canvas size to become smaller # than MinSize in wx backend. So the canvas size (e.g., (640, 480))may # be large than the window size. self.canvas.SetMinSize((1, 1)) #self.canvas.manager = self self.num = num if title is None: title = 'Figure %d' % self.num self.title = title self.isdestory = False szAll = wx.BoxSizer(wx.VERTICAL) self.figure.set_label(title) self.toolbar = Toolbar(self.canvas, self.figure) szAll.Add(self.toolbar, 0, wx.EXPAND) szAll.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW) self.toolbar.update() self.SetSizer(szAll) self.figmgr = FigureManagerWx(self.canvas, num, self) self.Bind(wx.EVT_CLOSE, self._onClose) self.canvas.mpl_connect('button_press_event', self._onClick) dp.connect(self.simLoad, 'sim.loaded') self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu) self.Bind(wx.EVT_MENU, self.OnProcessCommand, id=self.clsID_delete_datatip) self.Bind(wx.EVT_MENU, self.OnProcessCommand, id=self.clsID_clear_datatip) self.Bind(wx.EVT_MENU, self.OnProcessCommand, id=wx.ID_NEW) def GetToolBar(self): """Override wxFrame::GetToolBar as we don't have managed toolbar""" return self.toolbar def simLoad(self, num): for l in self.figure.gca().lines: if hasattr(l, 'trace'): sz = len(l.get_ydata()) for s in l.trace: if (not s) or (not s.startswith(str(num) + '.')): continue #dispatcher.send(signal='sim.trace_buf', objects=s, size=sz) def _onClick(self, event): if event.dblclick: self.toolbar.home() def OnProcessCommand(self, evt): if self.toolbar.datacursor.ProcessCommand(evt.GetId()): self.canvas.draw() def _show_context_menu(self): if self.toolbar.mode != 'datatip': return menu = wx.Menu() delMenu = menu.Append(self.clsID_delete_datatip, "Delete current datatip") note = self.toolbar.datacursor.active delMenu.Enable((note is not None) and note.get_visible()) menu.Append(self.clsID_clear_datatip, "Delete all datatips") self.PopupMenu(menu) menu.Destroy() def OnContextMenu(self, event): # Show menu after the current and pending event handlers have been # completed, otherwise it causes the following error in some system # (e.g., xubuntu, matplotlib 3.2.2, wx 4.1.0), and the menu doesn't show. # GLib-GObject-CRITICAL **: g_object_set_data: assertion 'G_IS_OBJECT # (object)' failed wx.CallAfter(self._show_context_menu) def _onClose(self, evt): self.canvas.close_event() self.canvas.stop_event_loop() Gcf.destroy(self.num) def destroy(self, *args): if self.isdestory is False: dp.send('frame.delete_panel', panel=self) wx.WakeUpIdle() def Destroy(self, *args, **kwargs): self.isdestory = True self.canvas.close_event() self.canvas.stop_event_loop() Gcf.destroy(self.num) return super(MatplotPanel, self).Destroy(*args, **kwargs) def GetTitle(self): """return the figure title""" return self.title def SetTitle(self, title): """set the figure title""" if title == self.title: return self.title = title dp.send('frame.update_panel_title', pane=self, title=self.title) def show(self): """show figure""" if self.IsShown() is False: self.canvas.draw() dp.send('frame.show_panel', panel=self) def update_buffer(self, bufs): """update the data used in plot_trace""" for l in self.figure.gca().lines: if hasattr(l, 'trace'): x = l.trace[0] y = l.trace[1] if x is None: if y in bufs: l.set_data(numpy.arange(len(bufs[y])), bufs[y]) elif x in bufs or y in bufs: xd = l.get_xdata() yd = l.get_ydata() if y in bufs: yd = bufs[y] if x in bufs: xd = bufs[x] if len(xd) != len(yd): sz = min(len(xd), len(yd)) xd = xd[0:sz] yd = yd[0:sz] l.set_data(xd, yd) if hasattr(l, 'autorelim') and l.autorelim: #Need both of these in order to rescale self.figure.gca().relim() self.figure.gca().autoscale_view() self.canvas.draw() def plot_trace(self, x, y, autorelim, *args, **kwargs): """plot and trace""" if y is None: return if x is None: l, = self.figure.gca().plot(list(y.values())[0], *args, **kwargs) l.trace = [None, list(y.keys())[0]] else: xd = list(x.values())[0] yd = list(y.values())[0] if len(xd) != len(yd): sz = min(len(xd), len(yd)) if sz > 0: xd = xd[0:sz] yd = yd[0:sz] else: xd = 0 yd = 0 l, = self.figure.gca().plot(xd, yd, *args, **kwargs) l.trace = [list(x.keys())[0], list(y.keys())[0]] l.autorelim = autorelim self.canvas.draw() def set_window_title(self, label): pass @classmethod def setactive(cls, pane): """set the active figure""" if pane and isinstance(pane, MatplotPanel): Gcf.set_active(pane) @classmethod def addFigure(cls, title=None, num=None, thisFig=None): direction = cls.kwargs.get('direction', 'top') fig = cls(cls.clsFrame, title=title, num=num, thisFig=thisFig) # set the minsize to be large enough to avoid some following assert; it # will not eliminate all as if a page is added to a notebook, the # minsize of notebook is not the max of all its children pages (check # frameplus.py). # wxpython/ext/wxWidgets/src/gtk/bitmap.cpp(539): assert ""width > 0 && # height > 0"" failed in Create(): invalid bitmap size dp.send('frame.add_panel', panel=fig, direction=direction, title=fig.GetTitle(), target=Gcf.get_active(), minsize=(75, 75)) return fig @classmethod def Initialize(cls, frame, **kwargs): if cls.isInitialized: return cls.isInitialized = True cls.clsFrame = frame cls.kwargs = kwargs resp = dp.send('frame.add_menu', path='File:New:Figure', rxsignal='bsm.figure') if resp: cls.clsID_new_figure = resp[0][1] if cls.clsID_new_figure is not wx.NOT_FOUND: dp.connect(cls.ProcessCommand, 'bsm.figure') dp.connect(cls.Uninitialize, 'frame.exit') dp.connect(cls.Initialized, 'frame.initialized') dp.connect(cls.setactive, 'frame.activate_panel') dp.connect(cls.OnBufferChanged, 'sim.buffer_changed') @classmethod def Initialized(cls): dp.send('shell.run', command='from matplotlib.pyplot import *', prompt=False, verbose=False, history=False) @classmethod def OnBufferChanged(cls, bufs): """the buffer has be changes, update the plot_trace""" for p in Gcf.get_all_fig_managers(): p.update_buffer(bufs) @classmethod def Uninitialize(cls): """destroy the module""" Gcf.destroy_all() @classmethod def ProcessCommand(cls, command): """process the menu command""" if command == cls.clsID_new_figure: plt.figure()
class Graph: def __init__(self, wxPanel, wxMinSize): self.figure = Figure() self.axes = self.figure.add_subplot(111) self.canvas = FigureCanvas(wxPanel, -1, self.figure) self.canvas.SetMinSize(wxMinSize) self.toolbar = NavigationToolbar(self.canvas) self.toolbar.set_message = wxPanel.GetTopLevelParent( ).updateStatus # Overrides function as the default does not work self.nameTracker = NameTracker() # Real labels self.dataSetLabel = 'Data Set' self.bestFitLabel = 'Best Fit' self.residualsLabel = 'Residuals' # Arrays of all known plot data self.backupXData = None self.backupYData = None self.xData = None self.yData = None self.baseline = None self.initialFit = None self.bestFit = None self.residuals = None self.axes.grid(True) def __getLineIndexFromDisplayLabel(self, displayLabel): """ Searches the lines array in the axes for a line with the given label and returns its index. :param displayLabel: :return: """ lines = self.axes.get_lines() for i in range(len(lines)): if lines[i].get_label() == displayLabel: return i return -1 def __getLineIndexFromRealLabel(self, realLabel): return self.__getLineIndexFromDisplayLabel( self.nameTracker.getDisplayNameFromRealName(realLabel)) def getLegendList(self): """ Returns a string list of all of the lines in the legend, even those that are invisible and not displayed. :return: a string list of all legend elements """ return [line.get_label() for line in self.axes.get_lines()] def getLineAlpha(self, lineIndex): return self.axes.get_lines()[lineIndex].get_alpha() def getLineColor(self, lineIndex): return matplotlib.colors.to_hex( self.axes.get_lines()[lineIndex].get_color()) def getLineInfo(self, lineIndex): """ Returns the alpha, color, line style, visibility state, z order, and label of the line we are looking for, given the label of the line. :param lineIndex: :return: alpha, color, line style, visibility state, z order, and label of line """ line = self.axes.get_lines()[lineIndex] return line.get_alpha(), matplotlib.colors.to_hex( line.get_color()), line.get_linestyle(), line.get_visible( ), line.get_zorder(), line.get_label() def getLineStyle(self, lineIndex): """ Returns the linestyle of the line at the given index in the axes array. :param lineIndex: :return: """ return self.axes.get_lines()[lineIndex].get_linestyle() def getLineVisibilityState(self, lineIndex): """ Returns the visibility state of the line at the given index in the axes array. :param lineIndex: :return: """ return self.axes.get_lines()[lineIndex].get_visible() def getLineZOrder(self, lineIndex): """ Returns the zOrder of the line at the given index in the axes array. :param lineIndex: :return: """ return self.axes.get_lines()[lineIndex].get_zorder() def setColorMultipleLines(self, axesIndexes, color): lines = self.axes.get_lines() for index in axesIndexes: lines[index].set_color(color) def setPlotTitle(self, title): """ Renames the title of the graph. :param title: The new title of the graph. """ self.axes.set_title(title) def setXLabel(self, xLabel): """ Renames the x axis label on the graph. :param xLabel: The new label of the x axis """ self.axes.set_xlabel(xLabel) def setYLabel(self, yLabel): """ Renames the y axis label on the graph. :param yLabel: The new label of the y axis """ self.axes.set_ylabel(yLabel) def changeLineAttributesGivenIndex(self, index, alpha, color, lineStyle, visibility, zOrder, newLabel): """ Changes the attributes of the line in the axes array with the given index. :param index: :param alpha: :param color: :param lineStyle: :param visibility: :param zOrder: :param newLabel: :return: """ line = self.axes.get_lines()[index] if alpha is not None: line.set_alpha(alpha) if color is not None and color != '': line.set_color(color) if lineStyle is not None and lineStyle != '': line.set_linestyle(lineStyle) if visibility is not None: line.set_visible(visibility) if zOrder is not None: line.set_zorder(zOrder) if newLabel is not None and newLabel != '': if True: self.nameTracker.renameDisplayNameGivenOldDisplayName( line.get_label(), newLabel) line.set_label(newLabel) else: errorStr += 'Cannot rename ' + oldLabel + ' to ' + newLabel + ' because the name already exists.' def __plot(self, xData, yData, label, style=None, visible=True): """ Plots the given x and y data to the given axes. :param xData: :param yData: :param label: :param style: :param visible: """ if style is None: self.axes.plot(xData, yData, label=label, alpha=1, visible=visible) else: self.axes.plot(xData, yData, style, label=label, alpha=1, visible=visible) self.nameTracker.addEntry(label) def plotNewDataset(self, xData, yData): """ Plots a new signal. Will overwrite signal data as well as backup. Do not mistake for updateSignalData() as that function is intended for existing signals to modify. data. :param xData: :param yData: :return: """ self.xData = xData.copy() self.yData = yData.copy() self.backupXData = xData.copy() self.backupYData = yData.copy() self.__plot(xData, yData, self.dataSetLabel, 'b') def redrawCanvas(self): """ This function redraws the canvas on screen. Is necessary to call anytime that the plot is updated in anyway. :return: """ self.canvas.draw() def rescale(self): """ This function will fix the scaling with when there are different sized plots on screen. Is necessary to call when the use adds or removes a new segment to the graph. Will only scale visible plots. :return: """ self.axes.relim(True) self.axes.autoscale_view() self.toolbar.Update() def __removeLineGivenDisplayLabel(self, displayLabel): lines = self.axes.get_lines() for i in range(len(lines)): if displayLabel == lines[i].get_label(): lines.pop(i).remove() self.nameTracker.removeEntryGivenRealName(displayLabel) break # May not work. get displayLabel from realLabel if this does not work def __removeLineGivenRealLabel(self, realLabel): lines = self.axes.get_lines() for i in range(len(lines)): if lines[i].get_label() == realLabel: lines.pop(i).remove() self.nameTracker.removeEntryGivenRealName(realLabel) break def __removeLinesGivenRealLabelPrefix(self, realLabelPrefix): lines = self.axes.get_lines() segLen = len(realLabelPrefix) for i in reversed(range(len(lines))): line = lines[i] if self.nameTracker.getRealNameFromDisplayName( line.get_label())[:segLen] == realLabelPrefix: lines.pop(i).remove() self.nameTracker.removeAllEntriesWithRealPrefix(realLabelPrefix) def removeAllLinesInAxes(self): lines = self.axes.get_lines() for i in reversed(range(len(lines))): lines.pop(i).remove() self.nameTracker.clear() self.backupXData = None self.backupYData = None self.xData = None self.yData = None self.baseline = None self.initialFit = None self.bestFit = None self.residuals = None def removeBaseLine(self): self.__removeLineGivenDisplayLabel(self.dataSetLabel) self.baseline = None def removeInputSpecta(self): self.__removeLinesGivenRealLabelPrefix( self.realInputSpectraLabelPrefix) self.inputPredictedPeaksXData = None self.inputPredictedPeaksYData = None # TODO Identify which lines are being deleted and update fields def removeLinesWithAxeIndexes(self, indexes): """ Removes the lines at the given indexes. :param indexes: a sorted list of line indexes in axes lines array :return: """ lines = self.axes.get_lines() for i in reversed(indexes): realLabel = self.nameTracker.getRealNameFromDisplayName( lines[i].get_label()) if realLabel == self.dataSetLabel: self.baseline = None elif realLabel == self.bestFitLabel: self.bestFit = None elif realLabel == self.realInitialFitLabel: self.initialFit = None elif realLabel == self.realResidualLabel: self.residuals = None elif realLabel == self.realSignalLabel: self.xData = None self.yData = None self.backupXData = None self.backupYData = None # Remove mapping and decon data. elif realLabel[:len(self.realDeconPeakLabelPrefix )] == self.realDeconPeakLabelPrefix: print('Need to remove decon data') elif realLabel[:len(self.realGuessPeaksLabelPrefix )] == self.realGuessPeaksLabelPrefix: print('Need to remove guess peak') elif realLabel[:len(self.realInputSpectraLabelPrefix )] == self.realInputSpectraLabelPrefix: print('Need to remove input spectra') elif realLabel[:len(self.mappingPrefix)] == self.mappingPrefix: print('Need to remove mapping') lines.pop(i).remove() self.nameTracker.removeEntryGivenRealName(realLabel) def removeDataSet(self): self.__removeLineGivenRealLabel(self.dataSetLabel) self.xData = None self.yData = None self.backupXData = None self.backupYData = None def restoreOriginalPlot(self): """ Will replace the altered signal with its backup data and re-plots it. :return: """ self.xData = self.backupXData.copy() self.yData = self.backupYData.copy() self.updateSignalData(self.xData, self.yData) def updateLegend(self): """ Updates the legend to the graph. Will only display visible plots. If there are no visible plots, the legend will disappear. :return: """ lines = self.axes.get_lines() legendLines = [line for line in lines if line.get_visible()] if self.axes.get_legend() is not None and len(legendLines) == 0: self.axes.get_legend().remove() else: self.axes.legend(handles=legendLines) def updateGraph(self): """ Updates the legend, scale and graph canvas. These functions can be found separately in this class, but for convenience, this function compresses them all into one. Only use this function if you are updating all three! """ self.rescale() self.updateLegend() self.redrawCanvas()
class MultimeterTab(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, wx.ID_ANY) # instance variables ------------------------------------------------------------------------------------------- self.DUT_choice = 'f5560A' self.DMM_choice = 'f8588A' self.dmm = dmm(self) self.t = threading.Thread() self.flag_complete = True # Flag indicates any active threads (False) or thread completed (True) self.user_input = { 'autorange': True, 'always_voltage': True, 'mode': 0, 'amplitude': '', 'rms': 0, 'frequency': '', } self.frame = parent self.left_panel = wx.Panel(self, wx.ID_ANY) self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER) # LEFT Panel --------------------------------------------------------------------------------------------------- self.combo_DUT_choice = wx.ComboBox( self.left_panel, wx.ID_ANY, choices=["Fluke 5560A", "Fluke 5730A"], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.combo_DMM_choice = wx.ComboBox( self.left_panel, wx.ID_ANY, choices=["Fluke 884xA", "Fluke 8588A"], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.text_DUT_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_DMM_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.label_source = wx.StaticText(self.left_panel, wx.ID_ANY, "Fluke 5560A") self.btn_connect = wx.Button(self.left_panel, wx.ID_ANY, "Connect") self.btn_config = wx.Button(self.left_panel, wx.ID_ANY, "Config") self.checkbox_autorange = wx.CheckBox(self.left_panel, wx.ID_ANY, "Auto Range") self.text_amplitude = wx.TextCtrl(self.left_panel, wx.ID_ANY, "10uA") self.combo_rms_or_peak = wx.ComboBox(self.left_panel, wx.ID_ANY, choices=["RMS", "Peak"], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.text_frequency = wx.TextCtrl(self.left_panel, wx.ID_ANY, "1000") self.checkbox_always_voltage = wx.CheckBox(self.left_panel, wx.ID_ANY, "Always Voltage") self.spreadsheet = MyGrid(self.left_panel) self.btn_cleardata = wx.Button(self.left_panel, wx.ID_ANY, "Clear Data") self.checkbox_errorbar = wx.CheckBox(self.left_panel, wx.ID_ANY, "Error Bars") self.btn_start = wx.Button(self.left_panel, wx.ID_ANY, "RUN") self.combo_mode = wx.ComboBox(self.left_panel, wx.ID_ANY, choices=["Single", "Sweep"], style=wx.CB_DROPDOWN) self.btn_breakpoints = wx.Button(self.left_panel, wx.ID_ANY, "Breakpoints") # PLOT Panel --------------------------------------------------------------------------------------------------- self.figure = plt.figure(figsize=(1, 1)) # look into Figure((5, 4), 75) self.canvas = FigureCanvas(self.plot_panel, -1, self.figure) self.toolbar = NavigationToolbar(self.canvas) self.toolbar.Realize() self.x, self.y, self.std = np.NaN, np.NaN, np.NaN self.errorbars = False self.ax1 = self.figure.add_subplot(111) self.line, (self.err_top, self.err_btm), (self.bars, ) = self.ax1.errorbar( np.zeros(1), np.zeros(1), yerr=np.zeros(1), fmt='o', ecolor='red', capsize=4) # BINDINGS ===================================================================================================== # Configure Instruments ---------------------------------------------------------------------------------------- on_DUT_selection = lambda event: self._get_DUT_choice(event) self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_DUT_selection, self.combo_DUT_choice) on_DMM_selection = lambda event: self._get_DMM_choice(event) self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_DMM_selection, self.combo_DMM_choice) on_connect = lambda event: self.on_connect_instr(event) self.Bind(wx.EVT_BUTTON, on_connect, self.btn_connect) on_config = lambda event: self.config(event) self.Bind(wx.EVT_BUTTON, on_config, self.btn_config) on_toggle_autorange = lambda event: self.toggle_autorange(event) self.Bind(wx.EVT_CHECKBOX, on_toggle_autorange, self.checkbox_autorange) on_toggle_always_voltage = lambda event: self.toggle_always_voltage( event) self.Bind(wx.EVT_CHECKBOX, on_toggle_always_voltage, self.checkbox_always_voltage) on_cleardata = lambda event: self.cleardata(event) self.Bind(wx.EVT_BUTTON, on_cleardata, self.btn_cleardata) on_toggle_errorbar = lambda event: self.toggle_errorbar(event) self.Bind(wx.EVT_CHECKBOX, on_toggle_errorbar, self.checkbox_errorbar) # Run Measurement (start subprocess) --------------------------------------------------------------------------- on_run_event = lambda event: self.on_run(event) self.Bind(wx.EVT_BUTTON, on_run_event, self.btn_start) on_open_breakpoints = lambda event: open_breakpoints() self.Bind(wx.EVT_BUTTON, on_open_breakpoints, self.btn_breakpoints) on_combo_select = lambda event: self.lock_controls(event) self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_combo_select, self.combo_mode) self.__set_properties() self.__do_layout() self.__do_plot_layout() self.__do_table_header() self.cleardata(wx.Event) def __set_properties(self): self.SetBackgroundColour(wx.Colour(255, 255, 255)) self.canvas.SetMinSize((700, 490)) self.left_panel.SetBackgroundColour(wx.Colour(255, 255, 255)) self.plot_panel.SetBackgroundColour(wx.Colour(255, 255, 255)) self.text_DUT_report.SetMinSize((200, 23)) self.text_DMM_report.SetMinSize((200, 23)) self.checkbox_autorange.SetValue(1) self.combo_DUT_choice.SetSelection(0) self.combo_DUT_choice.SetMinSize((87, 23)) self.combo_DMM_choice.SetSelection(1) self.combo_DMM_choice.SetMinSize((87, 23)) self.btn_connect.SetMinSize((87, 23)) self.btn_start.SetMinSize((87, 23)) self.btn_breakpoints.SetMinSize((87, 23)) self.combo_rms_or_peak.SetSelection(0) self.combo_mode.SetSelection(0) self.checkbox_always_voltage.SetValue(0) self.checkbox_errorbar.SetValue(0) self.spreadsheet.CreateGrid(60, 3) self.spreadsheet.SetRowLabelSize(40) self.spreadsheet.SetColLabelValue(0, 'Frequency') self.spreadsheet.SetColLabelValue(1, 'Value') self.spreadsheet.SetColLabelValue(2, 'STD') self.spreadsheet.SetMinSize((300, 215)) self.combo_mode.SetMinSize((110, 23)) def __do_layout(self): sizer_2 = wx.GridSizer(1, 1, 0, 0) grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0) grid_sizer_left_panel = wx.GridBagSizer(0, 0) grid_sizer_left_sub_btn_row = wx.GridBagSizer(0, 0) grid_sizer_plot = wx.GridBagSizer(0, 0) # LEFT PANEL =================================================================================================== # TITLE -------------------------------------------------------------------------------------------------------- row = 0 label_1 = wx.StaticText(self.left_panel, wx.ID_ANY, "MULTIMETER") label_1.SetFont( wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_1, (row, 0), (1, 2), 0, 0) row += 1 static_line_1 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_1.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_1, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) # INSTRUMENT INFO --------------------------------------------------------------------------------------------- row += 1 grid_sizer_left_panel.Add(self.combo_DUT_choice, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_DUT_report, (row, 1), (1, 2), wx.BOTTOM | wx.LEFT, 5) row += 1 grid_sizer_left_panel.Add(self.combo_DMM_choice, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_DMM_report, (row, 1), (1, 2), wx.BOTTOM | wx.LEFT, 5) row += 1 grid_sizer_left_panel.Add(self.btn_connect, (row, 0), (1, 1), wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.btn_config, (row, 1), (1, 1), wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.checkbox_autorange, (row, 2), (1, 1), wx.ALIGN_CENTRE_VERTICAL | wx.BOTTOM, 5) # f5560A SETUP ------------------------------------------------------------------------------------------------- row += 1 self.label_source.SetFont( wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(self.label_source, (row, 0), (1, 2), wx.TOP, 10) row += 1 static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_2.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_2, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) row += 1 label_amplitude = wx.StaticText(self.left_panel, wx.ID_ANY, "Amplitude:") grid_sizer_left_panel.Add(label_amplitude, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_amplitude, (row, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) grid_sizer_left_panel.Add(self.combo_rms_or_peak, (row, 2), (1, 1), wx.BOTTOM | wx.LEFT, 5) row += 1 label_frequency = wx.StaticText(self.left_panel, wx.ID_ANY, "Frequency (Ft):") grid_sizer_left_panel.Add(label_frequency, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_frequency, (row, 1), (1, 1), wx.LEFT, 5) label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(Hz)") grid_sizer_left_panel.Add(label_Hz, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) row += 1 label_measure = wx.StaticText(self.left_panel, wx.ID_ANY, "Measure") label_measure.SetFont( wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_measure, (row, 0), (1, 1), wx.TOP, 10) grid_sizer_left_panel.Add(self.checkbox_always_voltage, (row, 1), (1, 3), wx.ALIGN_BOTTOM | wx.LEFT, 10) # RESULTS ------------------------------------------------------------------------------------------------------ row += 1 static_line_3 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_3.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_3, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) row += 1 grid_sizer_left_panel.Add(self.spreadsheet, (row, 0), (1, 3), wx.ALIGN_LEFT | wx.RIGHT | wx.EXPAND, 5) row += 1 grid_sizer_left_panel.Add(self.btn_cleardata, (row, 0), (1, 1), wx.LEFT | wx.TOP, 5) grid_sizer_left_panel.Add(self.checkbox_errorbar, (row, 1), (1, 1), wx.LEFT | wx.TOP, 5) grid_sizer_left_panel.AddGrowableRow(11) # BUTTONS ------------------------------------------------------------------------------------------------------ row += 1 static_line_4 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_4.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_4, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) row += 1 grid_sizer_left_sub_btn_row.Add(self.btn_start, (0, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL) grid_sizer_left_sub_btn_row.Add(self.combo_mode, (0, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) grid_sizer_left_sub_btn_row.Add(self.btn_breakpoints, (0, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) grid_sizer_left_panel.Add(grid_sizer_left_sub_btn_row, (row, 0), (1, 3), wx.ALIGN_TOP | wx.BOTTOM, 13) self.left_panel.SetSizer(grid_sizer_left_panel) # PLOT PANEL =================================================================================================== grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.AddGrowableRow(0) grid_sizer_plot.AddGrowableCol(0) self.plot_panel.SetSizer(grid_sizer_plot) # add to main panel -------------------------------------------------------------------------------------------- grid_sizer_1.Add(self.left_panel, 0, wx.EXPAND | wx.RIGHT, 5) grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5) grid_sizer_1.AddGrowableRow(0) grid_sizer_1.AddGrowableCol(1) sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0) self.SetSizer(sizer_2) self.Layout() # GET SELECTED INSTRUMENTS ========================================================================================= def _get_instr_choice(self, type, selection, text_ctrl): # Get [NEW] instr choice --------------------------------------------------------------------------------------- print(f"The {selection} has been selected.") new_choice = 'f' + selection.strip('Fluke ') # Get [PREVIOUS] instr choice ---------------------------------------------------------------------------------- if type.lower() == 'dut': current_choice = self.dmm.DUT_choice elif type.lower() == 'dmm': current_choice = self.dmm.DMM_choice else: raise ValueError( "invalid instrument type. Please specify whether 'dut' or 'dmm'." ) # Conditionally refresh GUI ------------------------------------------------------------------------------------ if not self.dmm.M.connected: # if instruments are not connected, then user is free to change the DMM choice pass elif self.dmm.DUMMY_DATA: # if using dummy data, then we only need to set DMM choice to True before running self.dmm.DMM_choice = self.DMM_choice elif new_choice != current_choice and self.dmm.M.connected: # if the selected instrument does not currently match the remote instrument connected, there's a problem. self.dmm.M.connected = False print( f"[WARNING] the {selection} is NOT the current remote instrument connected!" ) # set text box color to red (chantilly: #EAB9C1) text_ctrl.SetBackgroundColour(wx.Colour(234, 185, 193)) self.left_panel.Refresh() elif new_choice == self.dmm.DMM_choice: # if the selected instrument does match the remote instrument connected, reset the color if necessary self.dmm.M.connected = True # Reset color (white smoke: #F0F0F0) text_ctrl.SetBackgroundColour(wx.Colour(240, 240, 240)) self.left_panel.Refresh() return new_choice def _get_DUT_choice(self, evt): selection = self.combo_DUT_choice.GetValue() self.DUT_choice = self._get_instr_choice( type='dut', selection=selection, text_ctrl=self.text_DUT_report) self.label_source.SetLabelText(str(selection)) return self.DUT_choice def _get_DMM_choice(self, evt): selection = self.combo_DMM_choice.GetValue() self.DMM_choice = self._get_instr_choice( type='dmm', selection=selection, text_ctrl=self.text_DMM_report) return self.DMM_choice # CONFIGURE INSTR FOR MEASUREMENT ================================================================================== def config(self, evt): dlg = InstrumentDialog( self, [self.DUT_choice, self.DMM_choice], None, wx.ID_ANY, ) dlg.ShowModal() dlg.Destroy() def get_instruments(self): config_dict = ReadConfig() dut = config_dict[self.DUT_choice] dmm = config_dict[self.DMM_choice] instruments = { self.DUT_choice: { 'address': dut['address'], 'port': dut['port'], 'gpib': dut['gpib'], 'mode': dut['mode'] }, self.DMM_choice: { 'address': dmm['address'], 'port': dmm['port'], 'gpib': dmm['gpib'], 'mode': dmm['mode'] } } return instruments def set_ident(self, idn_dict): self.text_DUT_report.SetValue(idn_dict['DUT']) # DUT self.text_DMM_report.SetValue(idn_dict['DMM']) # current DMM def on_connect_instr(self, evt): wait = wx.BusyCursor() msg = "Establishing remote connections to instruments." busyDlg = wx.BusyInfo(msg, parent=self) print( '\nResetting connection. Closing communication with any connected instruments' ) self.text_DUT_report.Clear() self.text_DMM_report.Clear() self.dmm.DUT_choice = self.DUT_choice self.dmm.DMM_choice = self.DMM_choice # self.thread_this(self.dmm.connect, (self.get_instruments(),)) self.dmm.connect(self.get_instruments(), ) busyDlg = None del wait # ------------------------------------------------------------------------------------------------------------------ def lock_controls(self, evt): choice = self.combo_mode.GetSelection() if choice == 1: self.text_amplitude.Disable() self.combo_rms_or_peak.Disable() self.text_frequency.Disable() else: self.text_amplitude.Enable() self.combo_rms_or_peak.Enable() self.text_frequency.Enable() def toggle_controls(self): if self.text_amplitude.IsEnabled(): self.text_amplitude.Disable() self.combo_rms_or_peak.Disable() self.text_frequency.Disable() else: self.text_amplitude.Enable() self.combo_rms_or_peak.Enable() self.text_frequency.Enable() def toggle_autorange(self, evt): if self.checkbox_autorange.IsChecked(): print("[Update] Auto Ranging turned ON for Fluke 884xA.") else: print("[Update] Auto Ranging turned OFF for Fluke 884xA.") def toggle_always_voltage(self, evt): DMM_choice = {self.DMM_choice} if self.checkbox_always_voltage.IsChecked(): print( f"[Update] {DMM_choice} will always measure voltage. Use external shunt if sourcing current." ) else: print( f"[Update] {DMM_choice} will perform direct measurement of the DUT." ) # ------------------------------------------------------------------------------------------------------------------ def get_values(self): autorange = bool(self.checkbox_autorange.GetValue()) always_voltage = bool(self.checkbox_always_voltage.GetValue()) mode = self.combo_mode.GetSelection() rms = self.combo_rms_or_peak.GetSelection() amp_string = self.text_amplitude.GetValue() freq_string = self.text_frequency.GetValue() self.user_input = { 'autorange': autorange, 'always_voltage': always_voltage, 'mode': mode, 'amplitude': amp_string, 'rms': rms, 'frequency': freq_string, } # ------------------------------------------------------------------------------------------------------------------ def thread_this(self, func, arg=()): self.t = threading.Thread(target=func, args=arg, daemon=True) self.t.start() # ------------------------------------------------------------------------------------------------------------------ def on_run(self, evt): self.get_values() if not self.t.is_alive() and self.flag_complete: # start new thread self.thread_this(self.dmm.start, (self.user_input, )) self.checkbox_autorange.Disable() self.checkbox_always_voltage.Disable() self.btn_start.SetLabel('STOP') elif self.t.is_alive() and self.user_input['mode'] == 1: # stop sweep # https://stackoverflow.com/a/36499538 self.t.do_run = False self.checkbox_autorange.Enable() self.checkbox_always_voltage.Enable() self.btn_start.SetLabel('RUN') else: print('thread already running.') # ------------------------------------------------------------------------------------------------------------------ def __do_plot_layout(self): self.ax1.set_title('MULTIMETER') self.ax1.set_xlabel('FREQUENCY (kHz)') self.ax1.set_ylabel('AMPLITUDE') self.ax1.grid(True) self.figure.tight_layout() def toggle_errorbar(self, evt): if self.errorbars: self.errorbars = False print("[Update] Error bars have been turned OFF") else: self.errorbars = True print("[Update] Error bars have been turned ON") if hasattr(self.x, 'size'): y_err = self.std / np.sqrt(self.x.size) self.plot(yerr=y_err) def update_plot(self, x, y, std): self.results_update([x, y, std]) # TODO: np.NaN is always index 0. Should this be fixed? self.x = np.append(self.x, x) self.y = np.append(self.y, y) self.std = np.append(self.std, std) yerr = self.std / np.sqrt(self.x.size) self.plot(yerr) def plot(self, yerr=None): if self.errorbars and yerr is not None: yerr_top = self.y + yerr yerr_btm = self.y - yerr self.line.set_data(self.x, self.y) self.err_top.set_data(self.x, yerr_top) self.err_btm.set_data(self.x, yerr_btm) new_segments = [ np.array([[x, yt], [x, yb]]) for x, yt, yb in zip(self.x, yerr_top, yerr_btm) ] self.bars.set_segments(new_segments) else: self.line.set_data(self.x, self.y) self.err_top.set_ydata(None) self.err_btm.set_ydata(None) new_segments = [] self.bars.set_segments(new_segments) self.plot_redraw() def plot_redraw(self): try: self.ax1.relim() # recompute the ax.dataLim except ValueError: yerr = self.err_top.get_ydata() print( f'Are the lengths of x: {len(self.x)}, y: {len(self.y)}, and yerr: {len(yerr)} mismatched?' ) raise self.ax1.autoscale() # UPDATE PLOT FEATURES ----------------------------------------------------------------------------------------- self.figure.tight_layout() self.toolbar.update() # Not sure why this is needed - ADS self.canvas.draw() self.canvas.flush_events() def cleardata(self, evt): self.x, self.y, self.std = np.NaN, np.NaN, np.NaN self.spreadsheet.cleardata() self.plot() def __do_table_header(self): header = ['frequency', 'value', 'std'] self.spreadsheet.append_rows(header) def results_update(self, row): """ :param row: of type list :return: True iff rows successfully appended to spreadsheet (grid) """ # self.text_rms_report.SetLabelText(str(y)) # self.text_frequency_report.SetLabelText(str(x)) if isinstance(row, list): self.spreadsheet.append_rows(row) return True else: raise ValueError('Row to be appended not of type list.') def error_dialog(self, error_message): print(error_message) dial = wx.MessageDialog(None, str(error_message), 'Error', wx.OK | wx.ICON_ERROR) dial.ShowModal()
class StylePickerPanel(wx.Panel): """ Based on StylePickerPanel, a Panel for selecting a list of colours, and their order. :param parent: The parent window. :param id: An identifier for the panel. wx.ID_ANY is taken to mean a default. :param pos: The panel position. The value ``wx.DefaultPosition`` indicates a default position, chosen by either the windowing system or wxWidgets, depending on platform. :param size: The panel size. The value ``wx.DefaultSize`` indicates a default size, chosen by either the windowing system or wxWidgets, depending on platform. :param style: The window style. See wxPanel. :param name: The window name. :param label: Label for the panel :param selection_choices: A list of hex value choices to populate the 'selection' side of the panel with """ def __init__( self, parent: wx.Window, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.TAB_TRAVERSAL, name=wx.PanelNameStr, label="Choose Styles: ", selection_choices: List[str] = None, ): if selection_choices is None: selection_choices = default_styles[:] self.label = label self.selection_choices = selection_choices args = (parent, id, pos, size) kwds = {"style": style, "name": name} # begin wxGlade: StylePickerPanel.__init__ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL wx.Panel.__init__(self, *args, **kwds) self.main_panel = wx.Panel(self, wx.ID_ANY) self.move_panel = wx.Panel(self.main_panel, wx.ID_ANY) self.picker_list_box = wx.ListBox(self.main_panel, wx.ID_ANY, choices=[]) self.picker_figure = Figure() self.picker_canvas = FigureCanvas(self.main_panel, wx.ID_ANY, self.picker_figure) self.add_btn = wx.Button(self.main_panel, wx.ID_ANY, "Add 🡲") self.remove_btn = wx.Button(self.main_panel, wx.ID_ANY, "🡰 Remove") self.selection_list_box = wx.ListBox(self.main_panel, wx.ID_ANY, choices=[]) self.up_btn = wx.Button(self.main_panel, wx.ID_ANY, "🡱 Up") self.down_btn = wx.Button(self.main_panel, wx.ID_ANY, "🡳 Down") self.selection_figure = Figure() self.selection_canvas = FigureCanvas(self.main_panel, wx.ID_ANY, self.selection_figure) self.__set_properties() self.__do_layout() self.Bind(wx.EVT_LISTBOX, self.update_picker_preview, self.picker_list_box) self.Bind(wx.EVT_LISTBOX_DCLICK, self.add, self.picker_list_box) self.Bind(wx.EVT_BUTTON, self.add, self.add_btn) self.Bind(wx.EVT_BUTTON, self.remove, self.remove_btn) self.Bind(wx.EVT_LISTBOX, self.update_selection_preview, self.selection_list_box) self.Bind(wx.EVT_BUTTON, self.move_up, self.up_btn) self.Bind(wx.EVT_BUTTON, self.move_down, self.down_btn) # end wxGlade self.Bind(wx.EVT_LISTBOX_DCLICK, self.remove, self.selection_list_box) self.markers = { "point": '.', "pixel": ',', "circle": 'o', "triangle_down": 'v', "triangle_up": '^', "triangle_left": '<', "triangle_right": '>', "tri_down": '1', "tri_up": '2', "tri_left": '3', "tri_right": '4', "octagon": '8', "square": 's', "pentagon": 'p', "plus (filled)": 'P', "star": '*', "hexagon1": 'h', "hexagon2": 'H', "plus": '+', 'x': 'x', "x (filled)": 'X', "diamond": 'D', "thin_diamond": 'd', "caretleft": 4, "caretright": 5, "caretup": 6, "caretdown": 7, } for marker in self.selection_choices: for key in self.markers.keys(): if marker == self.markers[key]: self.selection_list_box.Append(key) if not self.selection_list_box.IsEmpty(): self.selection_list_box.SetSelection(0) for mark in filter(lambda x: x not in self.selection_choices, [self.markers[y] for y in self.markers]): for key in self.markers.keys(): if mark == self.markers[key]: self.picker_list_box.Append(key) self.picker_list_box.SetSelection(0) self.picker_axes = self.picker_figure.add_subplot(111) self.selection_axes = self.selection_figure.add_subplot(111) self.update_picker_preview() self.update_selection_preview() def __set_properties(self): # begin wxGlade: StylePickerPanel.__set_properties self.move_panel.SetMinSize((170, -1)) self.picker_list_box.SetMinSize((170, 256)) self.picker_canvas.SetMinSize((64, 64)) self.selection_list_box.SetMinSize((170, 256)) self.up_btn.SetMinSize((80, -1)) self.down_btn.SetMinSize((80, -1)) self.selection_canvas.SetMinSize((64, 64)) self.main_panel.SetMinSize((450, -1)) # end wxGlade def __do_layout(self): # begin wxGlade: StylePickerPanel.__do_layout parent_sizer = wx.BoxSizer(wx.HORIZONTAL) main_sizer = wx.BoxSizer(wx.VERTICAL) list_grid_sizer = wx.BoxSizer(wx.HORIZONTAL) sizer_4 = wx.BoxSizer(wx.VERTICAL) selection_preview_sizer = wx.BoxSizer(wx.HORIZONTAL) move_grid = wx.GridSizer(1, 2, 0, 5) sizer_3 = wx.BoxSizer(wx.VERTICAL) sizer_2 = wx.BoxSizer(wx.VERTICAL) picker_preview_sizer = wx.BoxSizer(wx.HORIZONTAL) grid_sizer = wx.GridSizer(1, 3, 10, 10) borders_label = wx.StaticText(self.main_panel, wx.ID_ANY, "Choose Styles: ") borders_label.SetMinSize((128, 20)) borders_label.SetFont( wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, '')) grid_sizer.Add(borders_label, 0, wx.BOTTOM, 7) grid_sizer.Add(self.move_panel, 1, wx.ALIGN_CENTER | wx.EXPAND, 0) main_sizer.Add(grid_sizer, 0, 0, 0) sizer_2.Add(self.picker_list_box, 5, wx.EXPAND, 0) sizer_2.Add((0, 0), 0, 0, 0) preview_label = wx.StaticText(self.main_panel, wx.ID_ANY, "Preview: ") picker_preview_sizer.Add(preview_label, 0, 0, 0) picker_preview_sizer.Add(self.picker_canvas, 1, wx.EXPAND, 0) sizer_2.Add(picker_preview_sizer, 0, wx.EXPAND | wx.TOP, 5) list_grid_sizer.Add(sizer_2, 1, wx.EXPAND, 0) sizer_3.Add(self.add_btn, 0, wx.ALIGN_CENTER | wx.BOTTOM, 10) sizer_3.Add(self.remove_btn, 0, wx.ALIGN_CENTER, 0) list_grid_sizer.Add(sizer_3, 5, wx.ALIGN_CENTER | wx.BOTTOM, 64) sizer_4.Add(self.selection_list_box, 5, wx.EXPAND, 0) move_grid.Add(self.up_btn, 0, wx.ALIGN_CENTER, 0) move_grid.Add(self.down_btn, 0, wx.ALIGN_CENTER, 0) sizer_4.Add(move_grid, 0, wx.EXPAND, 0) preview_label_1 = wx.StaticText(self.main_panel, wx.ID_ANY, "Preview: ") selection_preview_sizer.Add(preview_label_1, 0, 0, 0) selection_preview_sizer.Add(self.selection_canvas, 1, wx.EXPAND, 0) sizer_4.Add(selection_preview_sizer, 0, wx.EXPAND | wx.TOP, 5) list_grid_sizer.Add(sizer_4, 1, wx.EXPAND, 0) main_sizer.Add(list_grid_sizer, 0, 0, 0) static_line_11 = wx.StaticLine(self.main_panel, wx.ID_ANY) main_sizer.Add(static_line_11, 0, wx.BOTTOM | wx.EXPAND | wx.TOP, 5) self.main_panel.SetSizer(main_sizer) parent_sizer.Add(self.main_panel, 0, wx.ALL, 10) self.SetSizer(parent_sizer) parent_sizer.Fit(self) self.Layout() # end wxGlade borders_label.SetLabel(self.label) def move_up(self, event): # wxGlade: StylePickerPanel.<event_handler> self.move(-1) event.Skip() def move_down(self, event): # wxGlade: StylePickerPanel.<event_handler> self.move(1) event.Skip() def move(self, direction=1): selection = self.selection_list_box.GetSelection() selection_string = self.selection_list_box.GetString(selection) if self.selection_list_box.GetCount( ) == selection + direction or selection + direction < 0: return self.selection_list_box.Delete(selection) self.selection_list_box.InsertItems([selection_string], selection + direction) self.selection_list_box.SetSelection(selection + direction) def add(self, event): # wxGlade: StylePickerPanel.<event_handler> selection = self.picker_list_box.GetSelection() if selection == -1: return selection_string = self.picker_list_box.GetString(selection) if selection_string == '': return self.selection_list_box.Append(selection_string) self.picker_list_box.Delete(selection) self.update_picker_preview() event.Skip() def remove(self, event): # wxGlade: StylePickerPanel.<event_handler> selection = self.selection_list_box.GetSelection() if selection == -1: return selection_string = self.selection_list_box.GetString(selection) if selection_string == '': return self.picker_list_box.Append(selection_string) self.selection_list_box.Delete(self.selection_list_box.GetSelection()) self.update_selection_preview() event.Skip() def update_picker_preview(self, *_): # wxGlade: StylePickerPanel.<event_handler> self.update_preview(self.picker_list_box, self.picker_axes) self.picker_canvas.draw_idle() def update_selection_preview( self, *_): # wxGlade: StylePickerPanel.<event_handler> self.update_preview(self.selection_list_box, self.selection_axes) self.selection_canvas.draw_idle() def update_preview(self, list_obj, axes): axes.clear() axes.axis("off") selection_string = list_obj.GetStringSelection() if selection_string == '': return axes.scatter(1, 1, s=200, color="red", marker=self.markers[selection_string]) do_layout = __do_layout set_properties = __set_properties def get_selection(self): return [ self.markers[self.selection_list_box.GetString(item)] for item in range(self.selection_list_box.GetCount()) ] def _do_layout(self): return self.__do_layout() def _set_properties(self): return self.__set_properties()
class MyDemoPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, wx.ID_ANY) self.frame = parent self.left_panel = wx.Panel(self, wx.ID_ANY) self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER) # PLOT Panel --------------------------------------------------------------------------------------------------- self.figure = plt.figure(figsize=(1, 1)) # look into Figure((5, 4), 75) self.canvas = FigureCanvas(self.plot_panel, -1, self.figure) self.toolbar = NavigationToolbar(self.canvas) self.toolbar.Realize() # Plot objects ------------------------------------------------------------------------------------------------- self.ax1 = self.figure.add_subplot(311) self.ax2 = self.figure.add_subplot(312) self.ax3 = self.figure.add_subplot(313) self.temporal, = self.ax1.plot([], [], linestyle='-') self.temporal_sum, = self.ax2.plot([], [], linestyle='-', marker='') self.temporal_hilbert, = self.ax2.plot([], [], linestyle='-', marker='') self.spectral, = self.ax3.plot([], [], color='#C02942') self.spectral_envelope, = self.ax3.plot([], [], color='tab:blue') # Plot Annotations --------------------------------------------------------------------------------------------- # https://stackoverflow.com/a/38677732 self.arrow_dim_obj = self.ax3.annotate( "", xy=(0, 0), xytext=(0, 0), textcoords=self.ax3.transData, arrowprops=dict(arrowstyle='<->')) self.bar_dim_obj = self.ax3.annotate("", xy=(0, 0), xytext=(0, 0), textcoords=self.ax3.transData, arrowprops=dict(arrowstyle='|-|')) bbox = dict(fc="white", ec="none") self.dim_text = self.ax3.text(0, 0, "", ha="center", va="center", bbox=bbox) # BINDINGS ===================================================================================================== self.combo_window = wx.ComboBox(self.left_panel, wx.ID_ANY, choices=[ "Rectangular", "Bartlett", "Hanning", "Hamming", "Blackman" ], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.combo_bandwidth_shape = wx.ComboBox( self.left_panel, wx.ID_ANY, choices=["Flat-Top", "Gaussian"], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.text_ctrl_fc = wx.TextCtrl(self.left_panel, wx.ID_ANY, style=wx.TE_PROCESS_ENTER) self.text_ctrl_laser_bw = wx.TextCtrl(self.left_panel, wx.ID_ANY, style=wx.TE_PROCESS_ENTER) self.text_ctrl_mainlobe = wx.TextCtrl(self.left_panel, wx.ID_ANY, style=wx.TE_PROCESS_ENTER) self.text_ctrl_mainlobe.SetToolTip("Mainlobe width") self.text_ctrl_emitted_modes = wx.TextCtrl(self.left_panel, wx.ID_ANY, style=wx.TE_PROCESS_ENTER) self.text_ctrl_index = wx.TextCtrl(self.left_panel, wx.ID_ANY, style=wx.TE_PROCESS_ENTER) self.checkbox_random_phase = wx.CheckBox(self.left_panel, wx.ID_ANY, "Random Phase") self.report_runtime = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.report_laser_bw = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.report_wavelength = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.report_cavity_modes = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.report_cavity_length = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.report_df = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.report_longitudinal_modes = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.report_fwhm = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.report_fwhm_width = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) on_update = lambda event: self.update(event) self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_fc) self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_laser_bw) self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_mainlobe) self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_emitted_modes) self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_index) self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_update, self.combo_bandwidth_shape) self.Bind(wx.EVT_CHECKBOX, on_update, self.checkbox_random_phase) self.__set_properties() self.__do_layout() self.__do_plot_layout() self.update(wx.Event) def __set_properties(self): self.SetBackgroundColour(wx.Colour(240, 240, 240)) self.canvas.SetMinSize((700, 490)) self.combo_window.SetSelection(0) self.combo_bandwidth_shape.SetSelection(0) self.checkbox_random_phase.SetValue(0) self.text_ctrl_fc.SetValue("473.613") # (THz) self.text_ctrl_laser_bw.SetValue("0.1") self.text_ctrl_index.SetValue("1.0") self.text_ctrl_emitted_modes.SetValue("15") self.text_ctrl_mainlobe.SetValue("0.01") self.report_runtime.SetValue("--") self.report_laser_bw.SetValue("--") self.report_wavelength.SetValue("--") self.report_cavity_modes.SetValue("--") self.report_cavity_length.SetValue("--") self.report_df.SetValue("--") self.report_longitudinal_modes.SetValue("--") self.report_fwhm.SetValue("--") self.report_fwhm_width.SetValue("--") def __do_layout(self): sizer_2 = wx.GridSizer(1, 1, 0, 0) grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0) grid_sizer_plot = wx.GridBagSizer(0, 0) grid_sizer_left_panel = wx.GridBagSizer(0, 0) # LEFT PANEL --------------------------------------------------------------------------------------------------- # TITLE -------------------------------------------------------------------------------------------------------- row = 0 label_1 = wx.StaticText(self.left_panel, wx.ID_ANY, "LASER MODE-LOCKING") label_1.SetFont( wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_1, (row, 0), (1, 3), wx.LEFT | wx.RIGHT | wx.TOP, 5) row += 1 static_line_1 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_1.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_1, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) # PARAMETERS --------------------------------------------------------------------------------------------------- row += 2 lbl_settings = wx.StaticText(self.left_panel, wx.ID_ANY, "Parameters") lbl_settings.SetFont( wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(lbl_settings, (row, 0), (1, 3), wx.LEFT | wx.RIGHT, 5) row += 1 static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_2.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_2, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) row += 1 lbl_fc = wx.StaticText(self.left_panel, wx.ID_ANY, "Fc:") grid_sizer_left_panel.Add( lbl_fc, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_fc, (row, 1), (1, 1), wx.BOTTOM, 5) lbl_units_THz = wx.StaticText(self.left_panel, wx.ID_ANY, "(THz):") grid_sizer_left_panel.Add( lbl_units_THz, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) row += 1 lbl_laser_bw = wx.StaticText(self.left_panel, wx.ID_ANY, "Laser BW:") grid_sizer_left_panel.Add( lbl_laser_bw, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_laser_bw, (row, 1), (1, 1), wx.BOTTOM, 5) lbl_units_laser_bw = wx.StaticText(self.left_panel, wx.ID_ANY, "(x Fc)") grid_sizer_left_panel.Add( lbl_units_laser_bw, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) row += 1 lbl_emitted_modes = wx.StaticText(self.left_panel, wx.ID_ANY, "Emitted Modes:") lbl_emitted_modes.SetToolTip( "The number of emitted modes inside the cavity") grid_sizer_left_panel.Add( lbl_emitted_modes, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_emitted_modes, (row, 1), (1, 1), wx.BOTTOM, 5) row += 1 lbl_reflective_index = wx.StaticText(self.left_panel, wx.ID_ANY, "Reflective Index:") grid_sizer_left_panel.Add( lbl_reflective_index, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_index, (row, 1), (1, 1), wx.BOTTOM, 5) row += 1 label_bandwidth_shape = wx.StaticText(self.left_panel, wx.ID_ANY, "Gain Bandwidth Shape:") grid_sizer_left_panel.Add( label_bandwidth_shape, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.combo_bandwidth_shape, (row, 1), (1, 2), wx.BOTTOM, 5) row += 1 grid_sizer_left_panel.Add(self.checkbox_random_phase, (row, 1), (1, 1), wx.LEFT | wx.TOP, 5) # SAMPLING PARAMETERS ------------------------------------------------------------------------------------------ row += 1 lbl_results = wx.StaticText(self.left_panel, wx.ID_ANY, "SAMPLING") lbl_results.SetFont( wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(lbl_results, (row, 0), (1, 3), wx.LEFT | wx.RIGHT, 5) row += 1 static_line_3 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_3.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_3, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) row += 1 label_window = wx.StaticText(self.left_panel, wx.ID_ANY, "Windowing Function:") grid_sizer_left_panel.Add( label_window, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.combo_window, (row, 1), (1, 2), wx.BOTTOM, 5) row += 1 label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "Mainlobe Width:") grid_sizer_left_panel.Add( label_Hz, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_mainlobe, (row, 1), (1, 1), wx.BOTTOM, 5) label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "MLW (Hz)") grid_sizer_left_panel.Add(label_Hz, (row, 2), (1, 1), wx.BOTTOM, 5) # REPORT ------------------------------------------------------------------------------------------------------- row += 1 row += 1 lbl_results = wx.StaticText(self.left_panel, wx.ID_ANY, "REPORT") lbl_results.SetFont( wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(lbl_results, (row, 0), (1, 3), wx.LEFT | wx.RIGHT, 5) row += 1 static_line_3 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_3.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_3, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) row += 1 lbl_runtime = wx.StaticText(self.left_panel, wx.ID_ANY, "Total Runtime:") grid_sizer_left_panel.Add( lbl_runtime, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.report_runtime, (row, 1), (1, 1), wx.BOTTOM, 5) label_ps = wx.StaticText(self.left_panel, wx.ID_ANY, "(ps)") grid_sizer_left_panel.Add(label_ps, (row, 2), (1, 1), wx.BOTTOM, 5) row += 1 label_bandwidth_shape = wx.StaticText(self.left_panel, wx.ID_ANY, "Laser BW:") grid_sizer_left_panel.Add( label_bandwidth_shape, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.report_laser_bw, (row, 1), (1, 1), wx.BOTTOM, 5) label_THz = wx.StaticText(self.left_panel, wx.ID_ANY, "(THz)") grid_sizer_left_panel.Add(label_THz, (row, 2), (1, 1), wx.BOTTOM, 5) row += 1 label_wavelength = wx.StaticText(self.left_panel, wx.ID_ANY, "Wavelength, λ:") grid_sizer_left_panel.Add( label_wavelength, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.report_wavelength, (row, 1), (1, 1), wx.BOTTOM, 5) label_nm = wx.StaticText(self.left_panel, wx.ID_ANY, "(nm)") grid_sizer_left_panel.Add(label_nm, (row, 2), (1, 1), wx.BOTTOM, 5) row += 1 label_cavity_modes = wx.StaticText(self.left_panel, wx.ID_ANY, "Cavity Modes, m:") grid_sizer_left_panel.Add( label_cavity_modes, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.report_cavity_modes, (row, 1), (1, 2), wx.BOTTOM, 5) row += 1 label_cavity_length = wx.StaticText(self.left_panel, wx.ID_ANY, "Cavity Length, L:") grid_sizer_left_panel.Add( label_cavity_length, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.report_cavity_length, (row, 1), (1, 1), wx.BOTTOM, 5) label_nm = wx.StaticText(self.left_panel, wx.ID_ANY, "(mm)") grid_sizer_left_panel.Add(label_nm, (row, 2), (1, 1), wx.BOTTOM, 5) row += 1 label_df = wx.StaticText(self.left_panel, wx.ID_ANY, "Frequency Separation, df:") grid_sizer_left_panel.Add( label_df, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.report_df, (row, 1), (1, 1), wx.BOTTOM, 5) label_GHz = wx.StaticText(self.left_panel, wx.ID_ANY, "(GHz)") grid_sizer_left_panel.Add(label_GHz, (row, 2), (1, 1), wx.BOTTOM, 5) row += 1 label_longitudinal_modes = wx.StaticText(self.left_panel, wx.ID_ANY, "Longitudinal Modes:") grid_sizer_left_panel.Add( label_longitudinal_modes, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.report_longitudinal_modes, (row, 1), (1, 2), wx.BOTTOM, 5) row += 1 lbl_fwhm = wx.StaticText(self.left_panel, wx.ID_ANY, "FWHM:") grid_sizer_left_panel.Add( lbl_fwhm, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.report_fwhm, (row, 1), (1, 1), wx.BOTTOM, 5) label_blank = wx.StaticText(self.left_panel, wx.ID_ANY, "") grid_sizer_left_panel.Add(label_blank, (row, 2), (1, 1), wx.BOTTOM, 5) row += 1 lbl_fwhm_width = wx.StaticText(self.left_panel, wx.ID_ANY, "FWHM Width:") grid_sizer_left_panel.Add( lbl_fwhm_width, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.report_fwhm_width, (row, 1), (1, 1), wx.BOTTOM, 5) label_GHz = wx.StaticText(self.left_panel, wx.ID_ANY, "(GHz)") grid_sizer_left_panel.Add(label_GHz, (row, 2), (1, 1), wx.BOTTOM, 5) self.left_panel.SetSizer(grid_sizer_left_panel) # PLOT PANEL =================================================================================================== grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.AddGrowableRow(0) grid_sizer_plot.AddGrowableCol(0) self.plot_panel.SetSizer(grid_sizer_plot) # add to main panel -------------------------------------------------------------------------------------------- grid_sizer_1.Add(self.left_panel, 0, wx.EXPAND | wx.RIGHT, 5) grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5) grid_sizer_1.AddGrowableRow(0) grid_sizer_1.AddGrowableCol(1) sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0) self.SetSizer(sizer_2) self.Layout() def popup_dialog(self, message): print(message) dial = wx.MessageDialog(None, str(message), 'Error', wx.OK | wx.ICON_ERROR) dial.ShowModal() def get_values(self): fc = to_float(self.text_ctrl_fc.GetValue()) laser_bw = to_float(self.text_ctrl_laser_bw.GetValue()) emitted_modes = to_integer(self.text_ctrl_emitted_modes.GetValue()) refraction_index = to_float(self.text_ctrl_index.GetValue()) bandwidth_shape = str(self.combo_bandwidth_shape.GetValue()).lower() window = str(self.combo_window.GetValue()).lower() MLW = to_float(self.text_ctrl_mainlobe.GetValue()) random_phase = bool(self.checkbox_random_phase.GetValue()) return fc, laser_bw, emitted_modes, refraction_index, bandwidth_shape, window, MLW, random_phase def update(self, evt): try: params = self.get_values() data, plot_data, plot_limits = worker.worker(params) self.results_update(data) self.plot(plot_data, plot_limits) except ValueError as e: self.popup_dialog(e) # ------------------------------------------------------------------------------------------------------------------ def __do_plot_layout(self): self.ax1.set_title('SAMPLED TIMED SERIES DATA') self.ax1.set_xlabel('TIME (ps)') self.ax1.set_ylabel('AMPLITUDE') self.ax2.set_title('SUMMATION OF ALL MODES/TONES') self.ax2.set_xlabel('TIME (ps)') self.ax2.set_ylabel('AMPLITUDE') self.ax3.set_title('SPECTRAL DATA') self.ax3.set_xlabel('FREQUENCY (THz)') self.ax3.set_ylabel('MAGNITUDE (V)') self.ax3.grid() self.figure.align_ylabels([self.ax1, self.ax2, self.ax3]) self.figure.tight_layout() def plot(self, plot_data, plot_limits): xt, yt_list, yt, yt_envelope, xf_rfft, yf_rfft, yf_smooth = plot_data xt1_left, xt1_right, xt2_left, xt2_right, xf_left, xf_right, dim_left, dim_right, dim_height, dim_label, dim_label_pos = plot_limits xt_scale = 1e12 xf_scale = 1e12 # TEMPORAL ----------------------------------------------------------------------------------------------------- xt_delta = xt[1] - xt[0] yt_limit = int(xt1_right / xt_delta) yt_limit2 = int(xt2_right / xt_delta) self.ax1.clear() self.ax1.plot(xt[:yt_limit] * xt_scale, (yt_list[:, :yt_limit]).T) # All signals self.ax1.set_title('SAMPLED TIMED SERIES DATA') self.ax1.set_xlabel('TIME (ps)') self.ax1.set_ylabel('AMPLITUDE') self.temporal_sum.set_data( xt[:yt_limit2] * xt_scale, yt[:yt_limit2]) # The summation of all signals self.temporal_hilbert.set_data( xt[:yt_limit2] * xt_scale, yt_envelope[:yt_limit2]) # The envelope of the summation self.ax1.set_xlim(left=xt1_left * xt_scale, right=xt1_right * xt_scale) self.ax2.set_xlim(left=xt2_left * xt_scale, right=xt2_right * xt_scale) # SPECTRAL ----------------------------------------------------------------------------------------------------- self.spectral.set_data(xf_rfft / xf_scale, np.abs(yf_rfft)) # The spectral plot of sum self.spectral_envelope.set_data(xf_rfft / xf_scale, yf_smooth) # The spectral plot of sum self.ax3.set_xlim(left=xf_left / xf_scale, right=xf_right / xf_scale) # Arrow dimension line update ---------------------------------------------------------------------------------- # https://stackoverflow.com/a/48684902 ------------------------------------------------------------------------- self.arrow_dim_obj.xy = (dim_left, dim_height) self.arrow_dim_obj.set_position((dim_right, dim_height)) self.arrow_dim_obj.textcoords = self.ax3.transData # dimension text update ---------------------------------------------------------------------------------------- self.dim_text.set_position((dim_left + dim_label_pos, dim_height)) self.dim_text.set_text(dim_label) # REDRAW PLOT -------------------------------------------------------------------------------------------------- self.plot_redraw() def plot_redraw(self): try: self.ax1.relim() # recompute the ax.dataLim self.ax2.relim() # recompute the ax.dataLim self.ax3.relim() # recompute the ax.dataLim except MemoryError as e: raise ValueError(str(e)) self.ax1.margins(x=0) self.ax1.autoscale(axis='y') self.ax2.autoscale(axis='y') self.ax3.autoscale(axis='y') # UPDATE PLOT FEATURES ----------------------------------------------------------------------------------------- self.figure.tight_layout() self.toolbar.update() # Not sure why this is needed - ADS self.canvas.draw() self.canvas.flush_events() def results_update(self, data): wavelength, laser_bw, df_max, cavity_modes, cavity_length, cavity_df, longitudinal_modes, fwhm_val, fwhm_width, runtime = data self.report_runtime.SetValue(str(round(runtime * 1e12, 3))) self.report_laser_bw.SetValue(str(laser_bw / 1e12)) self.report_cavity_length.SetValue(str(laser_bw / 1e12)) self.report_wavelength.SetValue(str(round(wavelength * 1e9, 3))) self.report_df.SetValue(str(round(df_max / 1e9, 3))) self.report_cavity_modes.SetValue(str(cavity_modes)) self.report_cavity_length.SetValue(str(round(cavity_length * 1e3, 3))) self.report_longitudinal_modes.SetValue(str(longitudinal_modes)) self.report_fwhm.SetValue(str(round(fwhm_val, 2))) self.report_fwhm_width.SetValue(str(round(fwhm_width * 1e12, 3))) print('total runtime:', round(runtime * 1e12, 3), 'ps') print('laser bandwidth:', laser_bw / 1e12, 'THz') print('full wave, half maximum:', laser_bw / 1e12, 'THz') print('wavelength, lambda:', round(wavelength * 1e9, 3), 'nm') print() print('max frequency separation for number of emitted modes, df:', round(df_max / 1e9, 3), 'GHz') print('cavity modes, m:', cavity_modes) print('cavity length, L:', round(cavity_length * 1e2, 3), 'cm', round(cavity_length * 1e3, 3), '(mm)') print('frequency separation of cavity, df:', round(cavity_df / 1e9, 3), 'GHz') print('longitudinal modes supported:', longitudinal_modes) print() print('FWHM value:', round(fwhm_val, 2)) print('FWHM width:', round(fwhm_width * 1e12, 3), 'ps') print()
class wxMatplotPanel(scrolled.ScrolledPanel): """ The PlotPanel has a Figure and a Canvas. OnSize events simply set a flag, and the actually redrawing of the figure is triggered by an Idle event. """ def __init__(self, renderPanel, color=None, dpi=None, **kwargs): from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg from matplotlib.figure import Figure # initialize Panel if 'id' not in list(kwargs.keys()): kwargs['id'] = wx.ID_ANY if 'style' not in list(kwargs.keys()): kwargs['style'] = wx.NO_FULL_REPAINT_ON_RESIZE scrolled.ScrolledPanel.__init__(self, renderPanel, **kwargs) self.renderPanel = renderPanel # initialize matplotlib stuff self.figure = Figure(None, dpi) #self.canvas = NoRepaintCanvas( self, -1, self.figure ) self.canvas = FigureCanvasWxAgg(self, -1, self.figure) self.canvas.mpl_connect('button_press_event', self.onMousePress) self.canvas.mpl_connect('pick_event', self.onPick) sizer = wx.BoxSizer() sizer.Add(self.canvas, 1, wx.EXPAND) self.SetSizer(sizer) # self.SetAutoLayout(1) # self.SetupScrolling() self.SetColor(color) self._refresh = False self._updateDraw = False self.toolBar_ = None self.canvasZoomWidth = 1.0 self.Bind(wx.EVT_IDLE, self._onIdle) self.Bind(wx.EVT_SIZE, self._onSize) self.resfreshCounter = 0 self.needUpdateHack_ = False self.needDrawing = False self.refresh() def onPick(self, event): pass def onMousePress(self, event): pass def onZoomChanged(self): pass def onPanChanged(self): pass def getToolBar(self, parent=None): if not self.toolBar_: self.toolBar_ = wxAUIMatplotPanelToolbar(self, self.canvas, parent) return self.toolBar_ def SetColor(self, rgbtuple=None): """Set figure and canvas colours to be the same.""" if rgbtuple is None: rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get() clr = [c / 255. for c in rgbtuple] self.figure.set_facecolor(clr) self.figure.set_edgecolor(clr) self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple)) def _onSize(self, event): self._refresh = True def _onIdle(self, evt): if self.IsShownOnScreen(): if self.needDrawing: self.redraw() if self._refresh: self.refresh() self._refresh = False if self._updateDraw: swatch = Stopwatch(True) self.canvas.draw() if self.needUpdateHack_: self.needUpdateHack() self.needUpdateHack_ = False self._updateDraw = False if self.canvasZoomWidth == 1.0: self.SetupScrolling(False, False) print("draw: ", swatch.duration()) def updateDrawOnIdle(self): self._updateDraw = True def resizeOnIdle(self): self._refresh = True def refresh(self): swatch = Stopwatch(True) #pixels = tuple( self.GetParent().GetClientSize() ) self.resfreshCounter += 1 pixels = tuple([ int(self.GetSize()[0] * self.canvasZoomWidth), int(self.GetSize()[1] * 1.0) ]) # print self, self.resfreshCounter, pixels if self.canvas.GetMinSize( )[0] != pixels[0] \ or self.canvas.GetMinSize()[1] != pixels[1]: # print "resize canvas" # print "parent-size", self.renderPanel.GetSize() # print "self-size", self.GetSize() # print "tupel-size", pixels # to avoid _onSize loop under linux # if self.GetSize() != self.parent.GetClientSize(): # if self.GetSize() != pixels: #self.SetSize( pixels ) #self.canvas.SetSize( pixels ) self.canvas.SetMinSize(pixels) self.figure.set_size_inches( float(pixels[0]) / self.figure.get_dpi(), float(pixels[1]) / self.figure.get_dpi()) adjust = True if hasattr(self, 'cbar'): if self.cbar.active: adjust = False if pixels[0] > 50 and adjust: self.figure.subplotpars.update(left=50.0 / pixels[0], right=(pixels[0] - 20.0) / pixels[0]) for a in self.figure.axes: if hasattr(a, "update_params"): a.update_params() #a.set_position( a.figbox, which = 'original' ) a.set_position(a.figbox, which='both') #self.figure.subplots_adjust( left = 50.0 / pixels[ 0 ] ) #self.figure.subplots_adjust( right = ( pixels[ 0 ] - 20.0 ) / pixels[ 0 ] ) # self.canvas.draw() self.updateDrawOnIdle() self.needUpdateHack_ = True # print "refresh: ", swatch.duration() def draw(self): pass # abstract, to be overridden by child classes def needUpdateHack(self): pass
class DistortionAnalyzerTab(wx.Panel): def __init__(self, parent, frame): wx.Panel.__init__(self, parent, wx.ID_ANY) # instance variables ------------------------------------------------------------------------------------------- self.flag_complete = True # Flag indicates any active threads (False) or thread completed (True) self.t = threading.Thread() self.da = da(self) self.user_input = {} self.DUT_choice = 'f5560A' self.DMM_choice = 'f8588A' self.parent = parent self.frame = frame self.left_panel = wx.Panel(self, wx.ID_ANY) self.left_sub_panel = wx.Panel(self.left_panel, wx.ID_ANY) # amplitude/frequency panel self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER) # PANELS ======================================================================================================= # LEFT Panel --------------------------------------------------------------------------------------------------- self.combo_DUT_choice = wx.ComboBox(self.left_panel, wx.ID_ANY, choices=["Fluke 5560A", "Fluke 5730A"], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.text_DUT_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_DMM_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.label_source = wx.StaticText(self.left_panel, wx.ID_ANY, "Fluke 5560A") self.btn_connect = wx.Button(self.left_panel, wx.ID_ANY, "Connect") self.btn_config = wx.Button(self.left_panel, wx.ID_ANY, "Config") self.checkbox_1 = wx.CheckBox(self.left_panel, wx.ID_ANY, "Local") self.text_amplitude = wx.TextCtrl(self.left_sub_panel, wx.ID_ANY, "10uA") self.combo_rms_or_peak = wx.ComboBox(self.left_sub_panel, wx.ID_ANY, choices=["RMS", "Peak"], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.text_frequency = wx.TextCtrl(self.left_sub_panel, wx.ID_ANY, "1000") self.combo_mainlobe = wx.ComboBox(self.left_panel, wx.ID_ANY, choices=["Relative", "Absolute"], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.combo_mainlobe.SetToolTip("Mainlobe width can be set relative to the signal frequency\n" "or as an absolute width independent of signal frequency") self.text_mainlobe = wx.TextCtrl(self.left_panel, wx.ID_ANY, "100") self.label_mainlobe = wx.StaticText(self.left_panel, wx.ID_ANY, "MLW (Hz)") self.label_mainlobe.SetToolTip("Main Lobe Width") self.combo_filter = wx.ComboBox(self.left_panel, wx.ID_ANY, choices=["None", "100kHz", "2MHz", "2.4MHz", "3MHz"], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.combo_coupling = wx.ComboBox(self.left_panel, wx.ID_ANY, choices=["AC1M", "AC10M", "DC1M", "DC10M", "DCAuto"], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.label_fs_report = wx.StaticText(self.left_panel, wx.ID_ANY, "--") self.label_samples_report = wx.StaticText(self.left_panel, wx.ID_ANY, "--") self.label_aperture_report = wx.StaticText(self.left_panel, wx.ID_ANY, "--") self.text_rms_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_thdn_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_thd_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.btn_start = wx.Button(self.left_panel, wx.ID_ANY, "RUN") self.combo_selected_test = wx.ComboBox(self.left_panel, wx.ID_ANY, choices=["Single", "Sweep", "Single w/ shunt", "Sweep w/ shunt", "Continuous"], style=wx.CB_DROPDOWN) self.btn_breakpoints = wx.Button(self.left_panel, wx.ID_ANY, "Breakpoints") # PLOT Panel --------------------------------------------------------------------------------------------------- self.figure = plt.figure(figsize=(1, 1)) # look into Figure((5, 4), 75) self.canvas = FigureCanvas(self.plot_panel, -1, self.figure) self.toolbar = NavigationToolbar(self.canvas) self.toolbar.Realize() self.ax1 = self.figure.add_subplot(211) self.ax2 = self.figure.add_subplot(212) self.temporal, = self.ax1.plot([], [], linestyle='-') self.spectral, = self.ax2.plot([], [], color='#C02942') # BINDINGS ===================================================================================================== # Configure Instruments ---------------------------------------------------------------------------------------- on_DUT_selection = lambda event: self._get_DUT_choice(event) self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_DUT_selection, self.combo_DUT_choice) on_connect = lambda event: self.on_connect_instr(event) self.Bind(wx.EVT_BUTTON, on_connect, self.btn_connect) on_config = lambda event: self.config(event) self.Bind(wx.EVT_BUTTON, on_config, self.btn_config) on_mainlobe = lambda event: self.on_mainlobe_change(event) self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_mainlobe, self.combo_mainlobe) # Run Measurement (start subprocess) --------------------------------------------------------------------------- on_run_event = lambda event: self.on_run(event) self.Bind(wx.EVT_BUTTON, on_run_event, self.btn_start) on_open_breakpoints = lambda event: open_breakpoints() self.Bind(wx.EVT_BUTTON, on_open_breakpoints, self.btn_breakpoints) on_toggle = lambda event: self.toggle_panel(event) self.Bind(wx.EVT_CHECKBOX, on_toggle, self.checkbox_1) on_combo_select = lambda event: self.lock_controls(event) self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_combo_select, self.combo_selected_test) self.__set_properties() self.__do_layout() self.__do_plot_layout() def __set_properties(self): self.SetBackgroundColour(wx.Colour(255, 255, 255)) self.canvas.SetMinSize((700, 490)) self.left_panel.SetBackgroundColour(wx.Colour(255, 255, 255)) self.plot_panel.SetBackgroundColour(wx.Colour(255, 255, 255)) self.left_panel.SetMinSize((310, 502)) # self.left_sub_panel.SetBackgroundColour(wx.Colour(255, 0, 255)) self.plot_panel.SetMinSize((700, 502)) self.combo_DUT_choice.SetSelection(0) self.combo_DUT_choice.SetMinSize((87, 23)) self.btn_connect.SetMinSize((87, 23)) self.btn_start.SetMinSize((87, 23)) self.btn_breakpoints.SetMinSize((87, 23)) self.text_DUT_report.SetMinSize((200, 23)) self.text_DMM_report.SetMinSize((200, 23)) self.canvas.SetMinSize((700, 490)) self.checkbox_1.SetValue(0) self.combo_rms_or_peak.SetSelection(0) self.combo_mainlobe.SetSelection(1) self.combo_mainlobe.SetMinSize((87, 23)) self.combo_filter.SetSelection(1) self.combo_filter.SetMinSize((110, 23)) self.combo_coupling.SetSelection(0) self.combo_coupling.SetMinSize((110, 23)) self.combo_selected_test.SetSelection(0) self.combo_selected_test.SetMinSize((110, 23)) def __do_layout(self): sizer_2 = wx.GridSizer(1, 1, 0, 0) grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0) grid_sizer_left_panel = wx.GridBagSizer(0, 0) grid_sizer_left_sub_panel = wx.GridBagSizer(0, 0) grid_sizer_plot = wx.GridBagSizer(0, 0) # LEFT PANEL =================================================================================================== # TITLE -------------------------------------------------------------------------------------------------------- row = 0 label_1 = wx.StaticText(self.left_panel, wx.ID_ANY, "DISTORTION ANALYZER") label_1.SetFont(wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_1, (row, 0), (1, 3), 0, 0) row += 1 static_line_1 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_1.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_1, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) # INSTRUMENT INFO --------------------------------------------------------------------------------------------- row += 1 grid_sizer_left_panel.Add(self.combo_DUT_choice, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_DUT_report, (row, 1), (1, 2), wx.BOTTOM | wx.LEFT, 5) row += 1 label_DMM = wx.StaticText(self.left_panel, wx.ID_ANY, "Fluke 8588A") label_DMM.SetFont(wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_DMM, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_DMM_report, (row, 1), (1, 2), wx.BOTTOM | wx.LEFT, 5) row += 1 grid_sizer_left_panel.Add(self.btn_connect, (row, 0), (1, 1), wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.btn_config, (row, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) grid_sizer_left_panel.Add(self.checkbox_1, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) # f5560A SETUP ------------------------------------------------------------------------------------------------- row += 1 self.label_source.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(self.label_source, (row, 0), (1, 2), wx.TOP, 10) row += 1 static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_2.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_2, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) # SUB PANEL --------------------------------------------------------------------------------------------------- row += 1 label_amplitude = wx.StaticText(self.left_sub_panel, wx.ID_ANY, "Amplitude:") label_amplitude.SetMinSize((87, 16)) grid_sizer_left_sub_panel.Add(label_amplitude, (0, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_sub_panel.Add(self.text_amplitude, (0, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) grid_sizer_left_sub_panel.Add(self.combo_rms_or_peak, (0, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) label_frequency = wx.StaticText(self.left_sub_panel, wx.ID_ANY, "Frequency (Ft):") label_frequency.SetMinSize((87, 16)) grid_sizer_left_sub_panel.Add(label_frequency, (1, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_sub_panel.Add(self.text_frequency, (1, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) label_Hz = wx.StaticText(self.left_sub_panel, wx.ID_ANY, "(Hz)") grid_sizer_left_sub_panel.Add(label_Hz, (1, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) self.left_sub_panel.SetSizer(grid_sizer_left_sub_panel) grid_sizer_left_panel.Add(self.left_sub_panel, (row, 0), (1, 3), wx.LEFT, 0) # Measurement -------------------------------------------------------------------------------------------------- row += 1 label_measure = wx.StaticText(self.left_panel, wx.ID_ANY, "Measurement") label_measure.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_measure, (row, 0), (1, 3), wx.TOP, 10) row += 1 static_line_3 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_3.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_3, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) row += 1 grid_sizer_left_panel.Add(self.combo_mainlobe, (row, 0), (1, 1), 0, 0) grid_sizer_left_panel.Add(self.text_mainlobe, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) grid_sizer_left_panel.Add(self.label_mainlobe, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) row += 1 label_filter = wx.StaticText(self.left_panel, wx.ID_ANY, "Filter:") grid_sizer_left_panel.Add(label_filter, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) grid_sizer_left_panel.Add(self.combo_filter, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) row += 1 label_coupling = wx.StaticText(self.left_panel, wx.ID_ANY, "Coupling:") grid_sizer_left_panel.Add(label_coupling, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) grid_sizer_left_panel.Add(self.combo_coupling, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) row += 1 label_fs = wx.StaticText(self.left_panel, wx.ID_ANY, "Fs:") grid_sizer_left_panel.Add(label_fs, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) grid_sizer_left_panel.Add(self.label_fs_report, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) row += 1 label_samples = wx.StaticText(self.left_panel, wx.ID_ANY, "Samples:") grid_sizer_left_panel.Add(label_samples, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) grid_sizer_left_panel.Add(self.label_samples_report, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) row += 1 label_aperture = wx.StaticText(self.left_panel, wx.ID_ANY, "Aperture:") grid_sizer_left_panel.Add(label_aperture, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) grid_sizer_left_panel.Add(self.label_aperture_report, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) # REPORT ------------------------------------------------------------------------------------------------------- row += 1 static_line_4 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_4.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_4, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) row += 1 label_rms = wx.StaticText(self.left_panel, wx.ID_ANY, "RMS:") grid_sizer_left_panel.Add(label_rms, (row, 0), (1, 1), 0, 0) grid_sizer_left_panel.Add(self.text_rms_report, (row, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) row += 1 label_thdn = wx.StaticText(self.left_panel, wx.ID_ANY, "THD+N:") grid_sizer_left_panel.Add(label_thdn, (row, 0), (1, 1), 0, 0) grid_sizer_left_panel.Add(self.text_thdn_report, (row, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) row += 1 label_thd = wx.StaticText(self.left_panel, wx.ID_ANY, "THD:") grid_sizer_left_panel.Add(label_thd, (row, 0), (1, 1), 0, 0) grid_sizer_left_panel.Add(self.text_thd_report, (row, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) # BUTTONS ------------------------------------------------------------------------------------------------------ row += 1 static_line_9 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_9.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_9, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) row += 1 grid_sizer_left_panel.Add(self.btn_start, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL) grid_sizer_left_panel.Add(self.combo_selected_test, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) grid_sizer_left_panel.Add(self.btn_breakpoints, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) self.left_panel.SetSizer(grid_sizer_left_panel) # PLOT PANEL =================================================================================================== grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.AddGrowableRow(0) grid_sizer_plot.AddGrowableCol(0) self.plot_panel.SetSizer(grid_sizer_plot) # add to main panel -------------------------------------------------------------------------------------------- grid_sizer_1.Add(self.left_panel, 0, wx.EXPAND | wx.RIGHT, 0) grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5) grid_sizer_1.AddGrowableRow(0) grid_sizer_1.AddGrowableCol(1) sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0) self.SetSizer(sizer_2) self.Layout() # GET SELECTED INSTRUMENTS ========================================================================================= def _get_instr_choice(self, type, selection, text_ctrl): # Get [NEW] instr choice --------------------------------------------------------------------------------------- print(f"The {selection} has been selected.") new_choice = 'f' + selection.strip('Fluke ') # Get [PREVIOUS] instr choice ---------------------------------------------------------------------------------- if type.lower() == 'dut': current_choice = self.da.DUT_choice elif type.lower() == 'dmm': current_choice = self.da.DMM_choice else: raise ValueError("invalid instrument type. Please specify whether 'dut' or 'dmm'.") # Conditionally refresh GUI ------------------------------------------------------------------------------------ if not self.da.M.connected: # if instruments are not connected, then user is free to change the DMM choice pass elif self.da.DUMMY_DATA: # if using dummy data, then we only need to set DMM choice to True before running self.da.DMM_choice = self.DMM_choice elif new_choice != current_choice and self.da.M.connected: # if the selected instrument does not currently match the remote instrument connected, there's a problem. self.da.M.connected = False print(f"[WARNING] the {selection} is NOT the current remote instrument connected!") # set text box color to red (chantilly: #EAB9C1) text_ctrl.SetBackgroundColour(wx.Colour(234, 185, 193)) self.left_panel.Refresh() elif new_choice == self.da.DMM_choice: # if the selected instrument does match the remote instrument connected, reset the color if necessary self.da.M.connected = True # Reset color (white smoke: #F0F0F0) text_ctrl.SetBackgroundColour(wx.Colour(240, 240, 240)) self.left_panel.Refresh() return new_choice def _get_DUT_choice(self, evt): selection = self.combo_DUT_choice.GetValue() self.DUT_choice = self._get_instr_choice(type='dut', selection=selection, text_ctrl=self.text_DUT_report) self.label_source.SetLabelText(str(selection)) return self.DUT_choice # ------------------------------------------------------------------------------------------------------------------ def config(self, evt): dlg = InstrumentDialog(self, [self.DUT_choice, self.DMM_choice], None, wx.ID_ANY, ) dlg.ShowModal() dlg.Destroy() def get_instruments(self): config_dict = ReadConfig() dut = config_dict[self.DUT_choice] dmm = config_dict[self.DMM_choice] instruments = {self.DUT_choice: {'address': dut['address'], 'port': dut['port'], 'gpib': dut['gpib'], 'mode': dut['mode']}, 'f8588A': {'address': dmm['address'], 'port': dmm['port'], 'gpib': dmm['gpib'], 'mode': dmm['mode']}} return instruments def set_ident(self, idn_dict): self.text_DUT_report.SetValue(idn_dict['DUT']) # DUT self.text_DMM_report.SetValue(idn_dict['DMM']) # current DMM def on_connect_instr(self, evt): wait = wx.BusyCursor() msg = "Establishing remote connections to instruments." busyDlg = wx.BusyInfo(msg, parent=self) print('\nResetting connection. Closing communication with any connected instruments') self.text_DUT_report.Clear() self.text_DMM_report.Clear() self.da.DUT_choice = self.DUT_choice self.da.DMM_choice = self.DMM_choice # self.thread_this(self.da.connect, (self.get_instruments(),)) self.da.connect(self.get_instruments(),) busyDlg = None del wait # ------------------------------------------------------------------------------------------------------------------ def toggle_panel(self, evt): local = self.checkbox_1.GetValue() if not local: if not self.left_sub_panel.IsShown(): self.left_sub_panel.Show() print(f"{self.DUT_choice} is in REMOTE and will be controlled by software") else: self.left_sub_panel.Hide() print(f"{self.DUT_choice} is in LOCAL and will not be controlled by software") def lock_controls(self, evt): local = self.checkbox_1.GetValue() choice = self.combo_selected_test.GetSelection() if choice in (1, 3): if local: self.checkbox_1.SetValue(0) self.toggle_panel(evt) self.checkbox_1.Disable() self.text_amplitude.Disable() self.combo_rms_or_peak.Disable() self.text_frequency.Disable() else: self.checkbox_1.Enable() self.text_amplitude.Enable() self.combo_rms_or_peak.Enable() self.text_frequency.Enable() def toggle_controls(self): if self.text_amplitude.IsEnabled(): self.checkbox_1.Disable() self.text_amplitude.Disable() self.combo_rms_or_peak.Disable() self.text_frequency.Disable() else: self.checkbox_1.Enable() self.text_amplitude.Enable() self.combo_rms_or_peak.Enable() self.text_frequency.Enable() def on_mainlobe_change(self, evt): value = self.combo_mainlobe.GetValue() if value == 'Relative': self.text_mainlobe.SetValue('0.1') self.label_mainlobe.SetLabelText('(MLW/f0)') self.label_mainlobe.SetToolTip("Relative Main Lobe Width (MLW)\nwith respect to the fundamental") else: self.text_mainlobe.SetValue('100') self.label_mainlobe.SetLabelText('MLW (Hz)') self.label_mainlobe.SetToolTip("Main Lobe Width") # ------------------------------------------------------------------------------------------------------------------ def get_values(self): selected_test = self.combo_selected_test.GetSelection() local = self.checkbox_1.GetValue() # local if True (1) mainlobe_type = self.combo_mainlobe.GetValue().lower() mainlobe_value = float(self.text_mainlobe.GetValue()) rms = self.combo_rms_or_peak.GetSelection() coupling = self.combo_coupling.GetValue() filter = self.combo_filter.GetValue() amp_string = self.text_amplitude.GetValue() freq_string = self.text_frequency.GetValue() self.user_input = {'selected_test': selected_test, 'local': local, 'amplitude': amp_string, 'frequency': freq_string, 'rms': rms, 'coupling': coupling, 'mainlobe_type': mainlobe_type, 'mainlobe_value': mainlobe_value, 'filter': filter } # ------------------------------------------------------------------------------------------------------------------ def thread_this(self, func, arg=()): self.t = threading.Thread(target=func, args=arg, daemon=True) self.t.start() # ------------------------------------------------------------------------------------------------------------------ def on_run(self, evt): self.get_values() if not self.t.is_alive() and self.flag_complete: # start new thread self.thread_this(self.da.start, (self.user_input,)) self.btn_start.SetLabel('STOP') elif self.t.is_alive() and self.user_input['selected_test'] in (1, 4): # stop continuous # https://stackoverflow.com/a/36499538 self.t.do_run = False self.btn_start.SetLabel('RUN') else: print('thread already running.') # ------------------------------------------------------------------------------------------------------------------ def __do_plot_layout(self): self.ax1.set_title('SAMPLED TIMED SERIES DATA') self.ax1.set_xlabel('TIME (ms)') self.ax1.set_ylabel('AMPLITUDE') self.ax2.set_title('DIGITIZED WAVEFORM SPECTRAL RESPONSE') self.ax2.set_xlabel('FREQUENCY (kHz)') self.ax2.set_ylabel('MAGNITUDE (dB)') self.ax2.grid() self.figure.align_ylabels([self.ax1, self.ax2]) self.figure.tight_layout() def plot(self, params): # TEMPORAL ----------------------------------------------------------------------------------------------------- xt = params['xt'] yt = params['yt'] self.temporal.set_data(xt, yt) xt_left = params['xt_left'] xt_right = params['xt_right'] yt_btm = params['yt_btm'] yt_top = params['yt_top'] yt_tick = params['yt_tick'] self.ax1.set_xlim(left=xt_left, right=xt_right) self.ax1.set_yticks(np.arange(yt_btm, yt_top, yt_tick)) # SPECTRAL ----------------------------------------------------------------------------------------------------- xf = params['xf'] yf = params['yf'] self.spectral.set_data(xf, yf) xf_left = params['xf_left'] xf_right = params['xf_right'] yf_btm = params['yf_btm'] yf_top = params['yf_top'] self.ax2.set_xlim(left=xf_left, right=xf_right) self.ax2.set_ylim(bottom=yf_btm, top=yf_top) # REDRAW PLOT -------------------------------------------------------------------------------------------------- self.plot_redraw() def plot_redraw(self): try: self.ax1.relim() # recompute the ax.dataLim except ValueError: xt_length = len(self.ax1.get_xdata()) yt_length = len(self.ax1.get_ydata()) print(f'Are the lengths of xt: {xt_length} and yt: {yt_length} mismatched?') raise self.ax1.margins(x=0) self.ax1.autoscale(axis='y') # UPDATE PLOT FEATURES ----------------------------------------------------------------------------------------- self.figure.tight_layout() self.toolbar.update() # Not sure why this is needed - ADS self.canvas.draw() self.canvas.flush_events() def results_update(self, results): amplitude = results['Amplitude'] freq_ideal = results['freq_ideal'] freq_sampled = results['freq_sampled'] fs = results['Fs'] N = results['N'] aperture = results['Aperture'] yrms = results['yrms'] units = results['units'] thdn = results['THDN'] thd = results['THD'] rms_noise = results['RMS NOISE'] self.label_fs_report.SetLabelText(str(fs)) self.label_samples_report.SetLabelText(str(N)) self.label_aperture_report.SetLabelText(str(aperture)) self.text_rms_report.SetValue(f"{'{:0.3e}'.format(yrms)} {units}") self.text_thdn_report.SetValue(f"{round(thdn * 100, 3)}% or {round(np.log10(thdn), 1)}dB") self.text_thd_report.SetValue(f"{round(thd * 100, 3)}% or {round(np.log10(thd), 1)}dB") row = [amplitude, freq_ideal, freq_sampled, yrms, thdn, thd, rms_noise, fs, N, aperture] self.frame.append_row(row) def error_dialog(self, error_message): print(error_message) dial = wx.MessageDialog(None, str(error_message), 'Error', wx.OK | wx.ICON_ERROR) dial.ShowModal()
class TestFrame(wx.Frame): def __init__(self, *args, **kwds): # begin wxGlade: TestFrame.__init__ kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) self.SetSize((1234, 669)) self.panel_frame = wx.Panel(self, wx.ID_ANY) self.thread = threading.Thread(target=self.run, args=()) self.thread.daemon = True self.row = 0 self.prevLine = '' self.line = '' self.table = {} self.overlay = {} self.ax = None self.x, self.y = [0.], [[0.]] self.flag_complete = False self.panel_main = wx.Panel(self.panel_frame, wx.ID_ANY) self.notebook = wx.Notebook(self.panel_main, wx.ID_ANY) self.notebook_program = wx.Panel(self.notebook, wx.ID_ANY) self.panel_3 = wx.Panel(self.notebook_program, wx.ID_ANY) self.text_ctrl_log = wx.TextCtrl(self.panel_3, wx.ID_ANY, "", style=wx.TE_MULTILINE | wx.TE_READONLY) self.text_ctrl_log_entry = wx.TextCtrl(self.panel_3, wx.ID_ANY, "", style=wx.TE_PROCESS_ENTER) sys.stdout = self.text_ctrl_log self.button_1 = wx.Button(self.panel_3, wx.ID_ANY, "Enter") self.notebook_1 = wx.Notebook(self.notebook_program, wx.ID_ANY) self.notebook_1_pane_2 = wx.Panel(self.notebook_1, wx.ID_ANY) self.figure = plt.figure(figsize=(2, 2)) # look into Figure((5, 4), 75) self.canvas = FigureCanvas(self.notebook_1_pane_2, -1, self.figure) self.toolbar = NavigationToolbar(self.canvas) self.toolbar.Realize() self.notebook_1_Settings = wx.Panel(self.notebook_1, wx.ID_ANY) # Use PropertyGridManger instead, if pages are desired self.property_grid_1 = wx.propgrid.PropertyGrid( self.notebook_1_Settings, wx.ID_ANY) self.notebook_Spreadsheet = wx.Panel(self.notebook, wx.ID_ANY) self.grid_1 = MyGrid(self.notebook_Spreadsheet) self.btn_run = wx.Button(self.panel_main, wx.ID_ANY, "Run Test") # TODO - Pause: https://stackoverflow.com/a/34313474/3382269 self.btn_pause = wx.Button(self.panel_main, wx.ID_ANY, "Pause") self.btn_stop = wx.Button(self.panel_main, wx.ID_ANY, "Stop") self.btn_save = wx.Button(self.panel_main, wx.ID_ANY, "Save") # Run Measurement (start subprocess) on_run_event = lambda event: self.on_run(event) self.Bind(wx.EVT_BUTTON, on_run_event, self.btn_run) self.Bind(wxpg.EVT_PG_CHANGED, self.OnGridChangeEvent) self.__set_properties() self.__do_layout() def __set_properties(self): # begin wxGlade: TestFrame.__set_properties self.SetTitle("Test Application") self.text_ctrl_log.SetMinSize((580, 410)) self.text_ctrl_log_entry.SetMinSize((483, 23)) self.canvas.SetMinSize((585, 406)) self.grid_1.CreateGrid(31, 16) self.grid_1.SetDefaultColSize(150) self.notebook.SetMinSize((1200, 500)) self._create_plot_properties() def __do_layout(self): # begin wxGlade: TestFrame.__do_layout sizer_1 = wx.BoxSizer(wx.VERTICAL) sizer_2 = wx.BoxSizer(wx.VERTICAL) grid_sizer_1 = wx.GridBagSizer(0, 0) sizer_4 = wx.BoxSizer(wx.VERTICAL) grid_sizer_2 = wx.GridSizer(1, 2, 0, 0) sizer_5 = wx.BoxSizer(wx.VERTICAL) sizer_3 = wx.BoxSizer(wx.VERTICAL) grid_sizer_4 = wx.GridBagSizer(0, 0) grid_sizer_3 = wx.GridBagSizer(0, 0) label_1 = wx.StaticText(self.panel_main, wx.ID_ANY, "Test Application") label_1.SetFont( wx.Font(20, wx.FONTFAMILY_DECORATIVE, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_1.Add(label_1, (0, 0), (1, 3), wx.ALIGN_CENTER_VERTICAL, 0) # bitmap_1 = wx.StaticBitmap(self.panel_main, wx.ID_ANY, wx.Bitmap("Fluke Logo.png", wx.BITMAP_TYPE_ANY)) # grid_sizer_1.Add(bitmap_1, (0, 3), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.ALL, 0) static_line_1 = wx.StaticLine(self.panel_main, wx.ID_ANY) static_line_1.SetMinSize((1200, 2)) grid_sizer_1.Add(static_line_1, (1, 0), (1, 4), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.TOP, 5) grid_sizer_3.Add(self.text_ctrl_log, (0, 0), (1, 2), wx.LEFT | wx.RIGHT | wx.TOP, 10) grid_sizer_3.Add(self.text_ctrl_log_entry, (1, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10) grid_sizer_3.Add(self.button_1, (1, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL, 0) self.panel_3.SetSizer(grid_sizer_3) grid_sizer_2.Add(self.panel_3, 1, wx.EXPAND, 0) grid_sizer_4.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_4.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND) sizer_3.Add(grid_sizer_4, 1, wx.EXPAND, 0) self.notebook_1_pane_2.SetSizer(sizer_3) sizer_5.Add(self.property_grid_1, 1, wx.EXPAND, 0) self.notebook_1_Settings.SetSizer(sizer_5) self.notebook_1.AddPage(self.notebook_1_pane_2, "Plot") self.notebook_1.AddPage(self.notebook_1_Settings, "Settings") grid_sizer_2.Add(self.notebook_1, 1, wx.EXPAND, 0) self.notebook_program.SetSizer(grid_sizer_2) sizer_4.Add(self.grid_1, 1, wx.EXPAND, 0) self.notebook_Spreadsheet.SetSizer(sizer_4) self.notebook.AddPage(self.notebook_program, "Program") self.notebook.AddPage(self.notebook_Spreadsheet, "Spreadsheet") grid_sizer_1.Add(self.notebook, (2, 0), (1, 4), wx.EXPAND, 0) static_line_2 = wx.StaticLine(self.panel_main, wx.ID_ANY) static_line_2.SetMinSize((1200, 2)) grid_sizer_1.Add(static_line_2, (3, 0), (1, 4), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.TOP, 5) grid_sizer_1.Add(self.btn_run, (4, 0), (1, 1), wx.ALIGN_RIGHT | wx.RIGHT, 20) grid_sizer_1.Add(self.btn_pause, (4, 1), (1, 1), wx.ALIGN_RIGHT | wx.RIGHT, 20) grid_sizer_1.Add(self.btn_stop, (4, 2), (1, 1), wx.ALIGN_RIGHT | wx.RIGHT, 10) grid_sizer_1.Add(self.btn_save, (4, 3), (1, 1), wx.ALIGN_RIGHT, 0) self.panel_main.SetSizer(grid_sizer_1) sizer_2.Add(self.panel_main, 1, wx.ALL | wx.EXPAND, 5) self.panel_frame.SetSizer(sizer_2) sizer_1.Add(self.panel_frame, 1, wx.EXPAND, 0) self.SetSizer(sizer_1) self.Layout() def _create_plot_properties(self): pg = self.property_grid_1 # pg.AddPage("Page 1 - Plot Settings") # if pages are needed, use PropertyGridManger instead dir_path = os.path.dirname(os.path.realpath(__file__)) pg.Append(wxpg.PropertyCategory("1 - Basic Properties")) pg.Append(wxpg.StringProperty(label="Title", value="Title")) pg.Append(wxpg.StringProperty(label="X Label", value="X Label")) pg.Append(wxpg.StringProperty(label="Y Label", value="Y Label")) pg.Append(wxpg.BoolProperty(label="Grid", value=True)) pg.SetPropertyAttribute(id="Grid", attrName="UseCheckbox", value=True) pg.Append(wxpg.PropertyCategory("2 - Data")) # https://discuss.wxpython.org/t/wxpython-pheonix-4-0-2-question-regarding-multichoiceproperty-and-setting-the-selection-of-the-choices/30044 # https://discuss.wxpython.org/t/how-to-set-propertygrid-values/30152/4 pg.Append( wxpg.EnumProperty( label="Scale", labels=['Linear', 'SemilogX', 'SemilogY', 'LogLog'])) pg.Append( wxpg.EnumProperty(label="X Data", name="X Data", labels=['NaN'], values=[0])) pg.Append( wxpg.MultiChoiceProperty(label="Y Data", name='Y Data', choices=['NaN'], value=['NaN'])) pg.Append( wxpg.EnumProperty(label="Data Labels", name="Data Labels", labels=['NaN'], values=[0])) pg.Append(wxpg.PropertyCategory("3 - Optional Static Plot Overlay")) pg.Append(wxpg.FileProperty(label="Overlay Plot", value=rf"{dir_path}")) pg.Append(wxpg.EnumProperty(label="X Axis Variable", labels=[''])) pg.Append(wxpg.EnumProperty(label="Y Axis Variable", labels=[''])) pg.Append(wxpg.PropertyCategory("4 - Advanced Properties")) pg.Append(wxpg.ArrayStringProperty(label="xLim", value=['0', '100'])) pg.Append(wxpg.ArrayStringProperty(label="yLim", value=['0', '100'])) pg.Append(wxpg.DateProperty(label="Date", value=wx.DateTime.Now())) pg.Append(wxpg.ColourProperty(label="Line Colour", value='#1f77b4')) pg.Append(wxpg.FontProperty(label="Font", value=self.GetFont())) pg.Grid.FitColumns() def on_run(self, event): self.thread.start() def run(self): print('run!') self.flag_complete = False T = Test(self) T.run() self.flag_complete = True def OnGridChangeEvent(self, evt): pg = self.property_grid_1 prop = evt.GetProperty() if prop.GetName() == 'Overlay Plot': self.get_static_overlay_data() choices = list(self.overlay.keys()) pg.GetProperty('X Axis Variable').SetChoices( wxpg.PGChoices(['Select option'] + choices)) pg.GetProperty('Y Axis Variable').SetChoices( wxpg.PGChoices(['Select option'] + choices)) elif prop.GetName( ) == 'X Axis Variable' or 'Y Axis Variable' and self.flag_complete: self.update_yAxisData() def get_static_overlay_data(self): pg = self.property_grid_1 file = pg.GetPropertyValue('Overlay Plot') # Read CSV file kwargs = { 'newline': '', 'encoding': "utf-8-sig" } # https://stackoverflow.com/a/49543191/3382269 mode = 'r' if sys.version_info < (3, 0): kwargs.pop('newline', None) mode = 'rb' with open(f'{file}', mode, **kwargs) as csvfile: spamreader = csv.reader(csvfile, delimiter=',', quotechar='"') for row in spamreader: if not self.overlay: self.overlay = {key: [] for key in row} else: for idx, key in enumerate(self.overlay.keys()): self.overlay[key].append(row[idx]) def write_header(self, header): if not self.table: self.table = {key: [] for key in header} else: self.table = { header[col]: self.table[key] for col, key in enumerate(self.table.keys()) } self.grid_1.write_list_to_row(self.row, self.table.keys()) self.row += 1 def write_to_log(self, row_data): self.grid_1.write_list_to_row(self.row, row_data) self.row += 1 if not self.table: self.table = { f'col {idx}': item for idx, item in enumerate(row_data) } else: for idx, key in enumerate(self.table.keys()): self.table[key].append(row_data[idx]) def plot_data(self): if not self.ax: self.draw_2dplot() else: pg = self.property_grid_1 # self.x = self.table[pg.GetProperty('X Data').GetValueAsString()] self.x = self.table[pg.GetPropertyValueAsString('X Data')] self.y = [self.table[col] for col in pg.GetPropertyValue('Y Data')] self.update_yAxisData() def _plot_helper(self): pg = self.property_grid_1 self.plot = [self.ax.plot] * len(self.y) for idx, y in enumerate(self.y): # plot returns a list of artists of which you want the first element when changing x or y data scale = pg.GetPropertyValueAsString('Scale') if scale == 'Linear': self.plot[idx], = self.ax.plot(self.x, y) self.draw_overlay(self.ax.plot) if scale == 'SemilogX': self.plot[idx], = self.ax.semilogx(self.x, y) self.draw_overlay(self.ax.semilogx) if scale == 'SemilogY': self.plot[idx], = self.ax.semilogy(self.x, y) self.draw_overlay(self.ax.semilogy) if scale == 'LogLog': self.plot[idx], = self.ax.loglog(self.x, y) self.draw_overlay(self.ax.loglog) def draw_overlay(self, plot_type): pg = self.property_grid_1 x_var = pg.GetPropertyValueAsString('X Axis Variable') y_var = pg.GetPropertyValueAsString('Y Axis Variable') if x_var and y_var != ('' or 'Select option'): plot_type(self.overlay[x_var], self.overlay[y_var]) def draw_2dplot(self): pg = self.property_grid_1 if self.table: choices = list(self.table.keys()) pg.GetProperty('X Data').SetChoices(wxpg.PGChoices(choices)) pg.GetProperty('Y Data').SetChoices(wxpg.PGChoices(choices)) pg.GetProperty('Data Labels').SetChoices( wxpg.PGChoices([""] + choices)) pg.SetPropertyValue('X Data', choices[0]) pg.SetPropertyValue('Y Data', [choices[1]]) self.x, self.y = self.table[choices[0]], [self.table[choices[1]]] else: self.x, self.y = [0.], [[0.]] self.ax = self.figure.add_subplot(111) self._plot_helper() self.update_axis_labels() self.figure.tight_layout() self.toolbar.update() # Not sure why this is needed - ADS def update_yAxisData(self): self.ax.clear() self._plot_helper() self.update_data_labels() self.update_axis_labels() self.ax.relim() self.ax.autoscale_view() self.canvas.draw() self.canvas.flush_events() def update_data_labels(self): pg = self.property_grid_1 label_var = pg.GetPropertyValueAsString('Data Labels') if label_var: for idx, text in enumerate(self.table[label_var]): plt.annotate(f'{label_var}={round(text, 3)}', (self.x[idx], self.y[0][idx]), xytext=(0, 10), textcoords='offset pixels', horizontalalignment='center') def update_axis_labels(self): pg = self.property_grid_1 self.ax.set_title(pg.GetPropertyValue('Title'), fontsize=15, fontweight="bold") self.ax.set_xlabel(pg.GetPropertyValue('X Label'), fontsize=8) self.ax.set_ylabel(pg.GetPropertyValue('Y Label'), fontsize=8) self.ax.grid(pg.GetPropertyValue('Grid')) self.plot[0].set_color( tuple(x / 255 for x in pg.GetPropertyValue('Line Colour')))
class Panel_Graphe(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, -1, style=wx.TAB_TRAVERSAL) self.afficher_valeurs = False self.panel = wx.Panel(self, -1, style=wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER) self.figure = matplotlib.pyplot.figure() self.canvas = Canvas(self.panel, -1, self.figure) self.canvas.SetMinSize((20, 20)) self.SetColor((255, 255, 255)) # Boutons self.bouton_apercu = wx.BitmapButton( self, -1, wx.Bitmap(Chemins.GetStaticPath(u"Images/16x16/Apercu.png"), wx.BITMAP_TYPE_ANY)) self.bouton_options = wx.BitmapButton( self, -1, wx.Bitmap(Chemins.GetStaticPath(u"Images/16x16/Mecanisme.png"), wx.BITMAP_TYPE_ANY)) # Binds self.Bind(wx.EVT_BUTTON, self.Apercu, self.bouton_apercu) self.Bind(wx.EVT_BUTTON, self.Options, self.bouton_options) # Properties self.bouton_apercu.SetToolTipString( _(u"Cliquez ici pour ouvrir le visualiseur de graphe pour accéder aux fonctions d'export et d'impression" )) self.bouton_options.SetToolTipString( _(u"Cliquez ici pour accéder aux options du graphe")) # Layout grid_sizer_base = wx.FlexGridSizer(1, 2, 5, 5) sizer_canvas = wx.BoxSizer(wx.VERTICAL) sizer_canvas.Add(self.canvas, 1, wx.EXPAND, 0) self.panel.SetSizer(sizer_canvas) grid_sizer_base.Add(self.panel, 1, wx.EXPAND | wx.TOP | wx.LEFT | wx.BOTTOM, 10) grid_sizer_boutons = wx.FlexGridSizer(5, 1, 5, 5) grid_sizer_boutons.Add(self.bouton_apercu, 0, 0, 0) grid_sizer_boutons.Add(self.bouton_options, 0, 0, 0) grid_sizer_base.Add(grid_sizer_boutons, 1, wx.EXPAND | wx.TOP | wx.BOTTOM | wx.RIGHT, 10) grid_sizer_base.AddGrowableCol(0) grid_sizer_base.AddGrowableRow(0) self.SetSizer(grid_sizer_base) self.Layout() def SetColor(self, rgbtuple=None): """Set figure and canvas colours to be the same.""" if rgbtuple is None: rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get() clr = [c / 255. for c in rgbtuple] self.figure.set_facecolor(clr) self.figure.set_edgecolor(clr) self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple)) def ConvertCouleur(self, couleur=None): return [c / 255. for c in couleur] def Apercu(self, event): import DLG_Zoom_graphe dlg = DLG_Zoom_graphe.Dialog(self, figure=self.figure) dlg.ShowModal() dlg.Destroy() def Options(self, event): # Création du menu contextuel menuPop = wx.Menu() item = wx.MenuItem(menuPop, 10, _(u"Afficher les valeurs"), _(u"Afficher les valeurs"), wx.ITEM_CHECK) menuPop.AppendItem(item) self.Bind(wx.EVT_MENU, self.On_afficher_valeurs, id=10) if self.afficher_valeurs == True: item.Check(True) self.PopupMenu(menuPop) menuPop.Destroy() def On_afficher_valeurs(self, event): self.afficher_valeurs = not self.afficher_valeurs self.MAJ() def SetDictBudget(self, dictBudget=None): self.dictBudget = dictBudget self.MAJ() def MAJ(self): self.figure.clear() if self.dictBudget == None: wx.CallAfter(self.SendSizeEvent) return # Récupération des données from Ol import OL_Suivi_budget analyse = OL_Suivi_budget.Analyse(self.dictBudget) listeCategories = analyse.GetValeurs() listeRealise = [] listeBudgete = [] listeLabels = [] for dictCategorie in listeCategories: listeRealise.append(dictCategorie["realise"]) listeBudgete.append(dictCategorie["plafond"]) listeLabels.append(dictCategorie["nomCategorie"]) ## if dictCategorie["typeCategorie"] == "debit" : ## solde = plafond - realise ## else : ## solde = realise - plafond ## # TEST ## listeIndex = np.arange(len(listeLabels)) ## bar_width = 0.2 ## opacity = 0.4 ## ## ax = self.figure.add_subplot(111) ## barres = ax.bar(listeIndex, listeRealise, width=bar_width, alpha=opacity, color="g", label=_(u"Réel")) ## barres = ax.bar(listeIndex + bar_width, listeBudgete, width=bar_width, alpha=opacity, color="b", label=_(u"Budgété")) ## ## # Formatage des montants sur y ## majorFormatter = FormatStrFormatter(SYMBOLE + u" %d") ## ax.yaxis.set_major_formatter(majorFormatter) ## ## # Affichage des labels x ## ax.set_xticks(listeIndex + bar_width) ## ax.set_xticklabels(listeLabels) ## ## labels = ax.get_xticklabels() ## setp(labels, rotation=45) ## ## # Légende ## props = matplotlib.font_manager.FontProperties(size=10) ## leg = ax.legend(loc='best', shadow=False, fancybox=True, prop=props) ## leg.get_frame().set_alpha(0.5) ## ## # Espaces autour du graph ## self.figure.subplots_adjust(left=0.12, bottom=0.40, right=None, wspace=None, hspace=None) # TEST listeIndex = np.arange(len(listeLabels)) bar_height = 0.2 opacity = 0.4 ax = self.figure.add_subplot(111) barresRealise = ax.barh(listeIndex, listeRealise, height=bar_height, alpha=opacity, color="g", label=_(u"Réel")) barresBudgete = ax.barh(listeIndex + bar_height, listeBudgete, height=bar_height, alpha=opacity, color="b", label=_(u"Budgété")) # Formatage des montants sur x majorFormatter = FormatStrFormatter(u"%d " + SYMBOLE) ax.xaxis.set_major_formatter(majorFormatter) # Affichage des labels x ax.set_yticks(listeIndex + bar_height) ax.set_yticklabels(listeLabels) def autolabel(rects): # attach some text labels for rect in rects: width = rect.get_width() ax.text(width + 20, rect.get_y() + rect.get_height() / 2., u"%.2f %s" % (int(width), SYMBOLE), ha='left', va='center', fontsize=8, color="grey") if self.afficher_valeurs == True: autolabel(barresRealise) autolabel(barresBudgete) # Recherche la largeur de texte max largeurMax = 0 for label in listeLabels: if len(label) > largeurMax: largeurMax = len(label) # Espaces autour du graph margeGauche = 0.1 + largeurMax * 0.008 self.figure.subplots_adjust(left=margeGauche, right=None, wspace=None, hspace=None) # Légende props = matplotlib.font_manager.FontProperties(size=10) leg = ax.legend(loc='best', shadow=False, fancybox=True, prop=props) leg.get_frame().set_alpha(0.5) # Finalisation ax.autoscale_view('tight') ## ax.grid(True) ax.figure.canvas.draw() wx.CallAfter(self.SendSizeEvent) return
class PlotPanel(BasePanel): """ MatPlotlib 2D plot as a wx.Panel, suitable for embedding in any wx.Frame. This does provide a right-click popup menu for configuration, zooming, saving an image of the figure, and Ctrl-C for copy-image-to-clipboard. For more features, see PlotFrame, which embeds a PlotPanel and also provides, a Menu, StatusBar, and Printing support. """ def __init__(self, parent, size=(700, 450), dpi=150, axisbg=None, facecolor=None, fontsize=9, trace_color_callback=None, output_title='plot', with_data_process=True, theme=None, **kws): self.trace_color_callback = trace_color_callback BasePanel.__init__(self, parent, output_title=output_title, size=size, **kws) self.conf = PlotConfig(panel=self, theme=theme, with_data_process=with_data_process) self.data_range = {} self.win_config = None self.cursor_callback = None self.lasso_callback = None self.cursor_mode = 'zoom' self.parent = parent self.figsize = (size[0]*1.0/dpi, size[1]*1.0/dpi) self.dpi = dpi self.conf.facecolor = ifnotNone(axisbg, self.conf.facecolor) self.conf.facecolor = ifnotNone(facecolor, self.conf.facecolor) # axesmargins : margins in px left/top/right/bottom self.axesmargins = (30, 30, 30, 30) self.BuildPanel() self.conf.user_limits = {} # [None, None, None, None] self.data_range = {} self.conf.zoom_lims = [] self.conf.axes_traces = {} self.use_dates = False self.dates_style = None def plot(self, xdata, ydata, side='left', title=None, xlabel=None, ylabel=None, y2label=None, use_dates=False, dates_style=None, **kws): """ create a new plot of x/y data, clearing any existing plot on the panel """ allaxes = self.fig.get_axes() if len(allaxes) > 1: for ax in allaxes[1:]: if ax in self.data_range: self.data_range.pop(ax) self.fig.delaxes(ax) self.data_range = {} self.conf.zoom_lims = [] self.conf.axes_traces = {} self.clear() axes = self.axes if side == 'right': axes = self.get_right_axes() self.conf.reset_lines() self.conf.yscale = 'linear' self.conf.user_limits[axes] = 4*[None] if xlabel is not None: self.set_xlabel(xlabel, delay_draw=True) if ylabel is not None: self.set_ylabel(ylabel, delay_draw=True) if y2label is not None: self.set_y2label(y2label, delay_draw=True) if title is not None: self.set_title(title, delay_draw=True) self.dates_style = ifnotNone(dates_style, self.dates_style) self.use_dates = ifnotNone(use_dates, self.use_dates) return self.oplot(xdata, ydata, side=side, **kws) def oplot(self, xdata, ydata, side='left', label=None, xlabel=None, ylabel=None, y2label=None, title=None, dy=None, ylog_scale=None, xlog_scale=None, grid=None, xmin=None, xmax=None, ymin=None, ymax=None, color=None, style=None, drawstyle=None, linewidth=2, marker=None, markersize=None, refresh=True, show_legend=None, legend_loc='best', legend_on=True, delay_draw=False, bgcolor=None, framecolor=None, gridcolor=None, labelfontsize=None, titlefontsize=None, legendfontsize=None, fullbox=None, axes_style=None, zorder=None, viewpad=None, theme=None, use_dates=None, dates_style=None, **kws): """ basic plot method, adding to an existing display """ self.cursor_mode = 'zoom' conf = self.conf conf.plot_type = 'lineplot' axes = self.axes if theme is not None: conf.set_theme(theme=theme) if side == 'right': axes = self.get_right_axes() # set y scale to log/linear if ylog_scale is not None: conf.yscale = {False:'linear', True:'log'}[ylog_scale] if xlog_scale is not None: conf.xscale = {False:'linear', True:'log'}[xlog_scale] axes.xaxis.set_major_formatter(FuncFormatter(self.xformatter)) self.dates_style = ifnotNone(dates_style, self.dates_style) self.use_dates = ifnotNone(use_dates, self.use_dates) if isinstance(xdata[0], datetime): self.use_dates = True if self.use_dates: # date handling options to get xdate to mpl dates # 1. xdate are in datetime: convert to mpl dates # 2. xdata are strings: parse with datestr2num # 3. xdata are floats: # a) dates_styles=='dates': use directly # b) else: convert as unix timestamp to mpl dates x0 = xdata[0] dstyle = self.dates_style if dstyle is None: dstyle = '' if isinstance(x0, datetime): xdata = dates.date2num(xdata) elif isinstance(x0, str) or dstyle.lower().startswith('str'): xdata = dates.datestr2num(xdata) elif not dstyle.lower().startswith('dates'): xdata = dates.epoch2num(xdata) linewidth = ifNone(linewidth, 2) conf.viewpad = ifnotNone(viewpad, conf.viewpad) if xlabel is not None: self.set_xlabel(xlabel, delay_draw=delay_draw) if ylabel is not None: self.set_ylabel(ylabel, delay_draw=delay_draw) if y2label is not None: self.set_y2label(y2label, delay_draw=delay_draw) if title is not None: self.set_title(title, delay_draw=delay_draw) if show_legend is not None: conf.set_legend_location(legend_loc, legend_on) conf.show_legend = show_legend conf.show_grid = ifnotNone(grid, conf.show_grid) # set data range for this trace # datarange = [min(xdata), max(xdata), min(ydata), max(ydata)] if axes not in conf.user_limits: conf.user_limits[axes] = [None, None, None, None] conf.user_limits[axes][0] = ifnotNone(xmin, conf.user_limits[axes][0]) conf.user_limits[axes][1] = ifnotNone(xmax, conf.user_limits[axes][1]) conf.user_limits[axes][2] = ifnotNone(ymin, conf.user_limits[axes][2]) conf.user_limits[axes][3] = ifnotNone(ymax, conf.user_limits[axes][3]) if axes == self.axes: axes.yaxis.set_major_formatter(FuncFormatter(self.yformatter)) else: axes.yaxis.set_major_formatter(FuncFormatter(self.y2formatter)) zorder = ifNone(zorder, 5*(conf.ntrace+1)) if axes not in conf.axes_traces: conf.axes_traces[axes] = [] conf.axes_traces[axes].append(conf.ntrace) conf.gridcolor = ifnotNone(gridcolor, conf.gridcolor) conf.facecolor = ifnotNone(bgcolor, conf.facecolor) if framecolor is not None: self.canvas.figure.set_facecolor(framecolor) conf.set_trace_zorder(zorder, delay_draw=True) if color: conf.set_trace_color(color, delay_draw=True) if style: conf.set_trace_style(style, delay_draw=True) if marker: conf.set_trace_marker(marker, delay_draw=True) if linewidth is not None: conf.set_trace_linewidth(linewidth, delay_draw=True) if markersize is not None: conf.set_trace_markersize(markersize, delay_draw=True) if drawstyle is not None: conf.set_trace_drawstyle(drawstyle, delay_draw=True) if dy is None: _lines = axes.plot(xdata, ydata, drawstyle=drawstyle, zorder=zorder) else: _lines = axes.errorbar(xdata, ydata, yerr=dy, zorder=zorder) if axes not in conf.data_save: conf.data_save[axes] = [] conf.data_save[axes].append((xdata, ydata)) if conf.show_grid and axes == self.axes: # I'm sure there's a better way... for i in axes.get_xgridlines() + axes.get_ygridlines(): i.set_color(conf.gridcolor) i.set_zorder(-100) axes.grid(True) else: axes.grid(False) if (self.conf.xscale == 'log' or self.conf.yscale == 'log'): self.set_logscale(xscale=self.conf.xscale, yscale=self.conf.yscale, delay_draw=delay_draw) if label is None: label = 'trace %i' % (conf.ntrace+1) conf.set_trace_label(label, delay_draw=True) needs_relabel = False if labelfontsize is not None: conf.labelfont.set_size(labelfontsize) needs_relabel = True if titlefontsize is not None: conf.titlefont.set_size(titlefontsize) needs_relabel = True if legendfontsize is not None: conf.legendfont.set_size(legendfontsize) needs_relabel = True if conf.ntrace < len(conf.lines): conf.lines[conf.ntrace] = _lines else: conf.init_trace(conf.ntrace, 'black', 'solid') conf.lines.append(_lines) # now set plot limits: if not delay_draw: self.set_viewlimits() if refresh: conf.refresh_trace(conf.ntrace) needs_relabel = True if conf.show_legend and not delay_draw: conf.draw_legend() if needs_relabel and not delay_draw: conf.relabel() # axes style ('box' or 'open') conf.axes_style = 'box' if fullbox is not None and not fullbox: conf.axes_style = 'open' if axes_style in ('open', 'box', 'bottom'): conf.axes_style = axes_style conf.set_axes_style(delay_draw=delay_draw) if not delay_draw: self.draw() self.canvas.Refresh() conf.ntrace = conf.ntrace + 1 return _lines def plot_many(self, datalist, side='left', title=None, xlabel=None, ylabel=None, **kws): """ plot many traces at once, taking a list of (x, y) pairs """ def unpack_tracedata(tdat, **kws): if (isinstance(tdat, dict) and 'xdata' in tdat and 'ydata' in tdat): xdata = tdat.pop('xdata') ydata = tdat.pop('ydata') out = kws out.update(tdat) elif isinstance(tdat, (list, tuple)): out = kws xdata = tdat[0] ydata = tdat[1] return (xdata, ydata, out) opts = dict(side=side, title=title, xlabel=xlabel, ylabel=ylabel, delay_draw=True) opts.update(kws) x0, y0, opts = unpack_tracedata(datalist[0], **opts) self.plot(x0, y0, **opts) for dat in datalist[1:]: x, y, opts = unpack_tracedata(dat, delay_draw=True) self.oplot(x, y, **opts) self.reset_formats() conf = self.conf if conf.show_legend: conf.draw_legend() conf.relabel() self.draw() self.canvas.Refresh() def add_text(self, text, x, y, side='left', size=None, rotation=None, ha='left', va='center', family=None, **kws): """add text at supplied x, y position """ axes = self.axes if side == 'right': axes = self.get_right_axes() dynamic_size = False if size is None: size = self.conf.legendfont.get_size() dynamic_size = True t = axes.text(x, y, text, ha=ha, va=va, size=size, rotation=rotation, family=family, **kws) self.conf.added_texts.append((dynamic_size, t)) self.draw() def add_arrow(self, x1, y1, x2, y2, side='left', shape='full', color='black', width=0.01, head_width=0.03, overhang=0, **kws): """add arrow supplied x, y position""" dx, dy = x2-x1, y2-y1 axes = self.axes if side == 'right': axes = self.get_right_axes() axes.arrow(x1, y1, dx, dy, shape=shape, length_includes_head=True, fc=color, edgecolor=color, width=width, head_width=head_width, overhang=overhang, **kws) self.draw() def scatterplot(self, xdata, ydata, label=None, size=10, color=None, edgecolor=None, selectcolor=None, selectedge=None, xlabel=None, ylabel=None, y2label=None, xmin=None, xmax=None, ymin=None, ymax=None, viewpad=None, title=None, grid=None, callback=None, **kw): if xlabel is not None: self.set_xlabel(xlabel) if ylabel is not None: self.set_ylabel(ylabel) if y2label is not None: self.set_y2label(y2label) if title is not None: self.set_title(title) if grid is not None: self.conf.show_grid = grid if callback is not None: self.lasso_callback = callback self.conf.plot_type = 'scatter' self.cursor_mode = 'lasso' if color is not None: self.conf.scatter_normalcolor = color if edgecolor is not None: self.conf.scatter_normaledge = edgecolor if selectcolor is not None: self.conf.scatter_selectcolor = selectcolor if selectedge is not None: self.conf.scatter_selectedge = selectedge if viewpad is not None: self.conf.viewpad = viewpad axes = self.axes self.conf.user_limits[axes] = [xmin, xmax, ymin, ymax] self.conf.axes_traces = {axes: [0]} self.conf.set_trace_label('scatterplot') # self.conf.set_trace_datarange((min(xdata), max(xdata), # min(ydata), max(ydata))) self.conf.scatter_xdata = xdata self.conf.scatter_ydata = ydata self.axes.scatter(xdata, ydata, c=self.conf.scatter_normalcolor, edgecolors=self.conf.scatter_normaledge) if self.conf.show_grid: for i in axes.get_xgridlines()+axes.get_ygridlines(): i.set_color(self.conf.gridcolor) i.set_zorder(-30) axes.grid(True) else: axes.grid(False) xrange = max(xdata) - min(xdata) yrange = max(ydata) - min(ydata) xmin = min(xdata) - xrange/25.0 xmax = max(xdata) + xrange/25.0 ymin = min(ydata) - yrange/25.0 ymax = max(ydata) + yrange/25.0 axes.set_xlim((xmin, xmax), emit=True) axes.set_ylim((ymin, ymax), emit=True) self.set_viewlimits() self.draw() def lassoHandler(self, vertices): conf = self.conf if self.conf.plot_type == 'scatter': xd, yd = conf.scatter_xdata, conf.scatter_ydata sdat = list(zip(xd, yd)) oldmask = conf.scatter_mask try: self.axes.scatter(xd[where(oldmask)], yd[where(oldmask)], s=conf.scatter_size, c=conf.scatter_normalcolor, edgecolors=conf.scatter_normaledge) except IndexError: self.axes.scatter(xd, yd, s=conf.scatter_size, c=conf.scatter_normalcolor, edgecolors=conf.scatter_normaledge) mask = conf.scatter_mask = inside_poly(vertices, sdat) pts = nonzero(mask)[0] self.axes.scatter(xd[where(mask)], yd[where(mask)], s=conf.scatter_size, c=conf.scatter_selectcolor, edgecolors=conf.scatter_selectedge) else: xdata = self.axes.lines[0].get_xdata() ydata = self.axes.lines[0].get_ydata() sdat = [(x, y) for x, y in zip(xdata, ydata)] mask = inside_poly(vertices,sdat) pts = nonzero(mask)[0] self.lasso = None self.draw() # self.canvas.draw_idle() if (self.lasso_callback is not None and hasattr(self.lasso_callback , '__call__')): self.lasso_callback(data = sdat, selected=pts, mask=mask) def set_xylims(self, limits, axes=None, side='left'): "set user-defined limits and apply them" if axes is None: axes = self.axes if side == 'right': axes = self.get_right_axes() self.conf.user_limits[axes] = list(limits) self.unzoom_all() def set_viewlimits(self): """updates xy limits of a plot based on current data, user defined limits, and any zoom level """ self.reset_formats() self.conf.set_viewlimits() def get_viewlimits(self, axes=None): if axes is None: axes = self.axes xmin, xmax = axes.get_xlim() ymin, ymax = axes.get_ylim() return (xmin, xmax, ymin, ymax) def clear(self): """ clear plot """ for ax in self.fig.get_axes(): ax.cla() self.conf.ntrace = 0 self.conf.xlabel = '' self.conf.ylabel = '' self.conf.y2label = '' self.conf.title = '' self.conf.data_save = {} def reset_config(self): """reset configuration to defaults.""" self.conf.set_defaults() def unzoom(self, event=None, **kws): """ zoom out 1 level, or to full data range """ self.reset_formats() self.conf.unzoom(full=False) def unzoom_all(self, event=None): """ zoom out full data range """ self.reset_formats() self.conf.unzoom(full=True) def process_data(self, event=None, expr=None): if expr in self.conf.data_expressions: self.conf.data_expr = expr self.conf.process_data() self.draw() if expr is None: expr = '' if self.conf.data_deriv: if expr is None: expr = 'y' expr = "deriv(%s)" % expr self.write_message("plotting %s" % expr, panel=0) def toggle_deriv(self, evt=None, value=None): "toggle derivative of data" if value is None: self.conf.data_deriv = not self.conf.data_deriv expr = self.conf.data_expr or '' if self.conf.data_deriv: expr = "deriv(%s)" % expr self.write_message("plotting %s" % expr, panel=0) self.conf.process_data() def set_logscale(self, event=None, xscale='linear', yscale='linear', delay_draw=False): "set log or linear scale for x, y axis" self.conf.set_logscale(xscale=xscale, yscale=yscale, delay_draw=delay_draw) def toggle_legend(self, evt=None, show=None): "toggle legend display" if show is None: show = not self.conf.show_legend self.conf.show_legend = show self.conf.draw_legend() def toggle_grid(self, evt=None, show=None): "toggle grid display" if show is None: show = not self.conf.show_grid self.conf.enable_grid(show) def configure(self, event=None): """show configuration frame""" if self.win_config is not None: try: self.win_config.Raise() except: self.win_config = None if self.win_config is None: self.win_config = PlotConfigFrame(parent=self, config=self.conf, trace_color_callback=self.trace_color_callback) self.win_config.Raise() #### ## create GUI #### def BuildPanel(self): """ builds basic GUI panel and popup menu""" self.fig = Figure(self.figsize, dpi=self.dpi) # 1 axes for now self.gridspec = GridSpec(1,1) self.axes = self.fig.add_subplot(self.gridspec[0], facecolor=self.conf.facecolor) self.canvas = FigureCanvas(self, -1, self.fig) self.canvas.SetClientSize((self.figsize[0]*self.dpi, self.figsize[1]*self.dpi)) self.canvas.SetMinSize((100, 100)) self.printer.canvas = self.canvas self.set_bg(self.conf.framecolor) self.conf.canvas = self.canvas self.canvas.SetCursor(wxCursor(wx.CURSOR_CROSS)) self.canvas.mpl_connect("pick_event", self.__onPickEvent) # overwrite ScalarFormatter from ticker.py here: self.axes.xaxis.set_major_formatter(FuncFormatter(self.xformatter)) self.axes.yaxis.set_major_formatter(FuncFormatter(self.yformatter)) # This way of adding to sizer allows resizing sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.canvas, 2, wx.LEFT|wx.TOP|wx.BOTTOM|wx.EXPAND, 0) # self.SetAutoLayout(True) self.autoset_margins() self.SetSizer(sizer) self.SetSize(self.GetBestVirtualSize()) canvas_draw = self.canvas.draw def draw(*args, **kws): self.autoset_margins() canvas_draw(*args, **kws) self.canvas.draw = draw self.addCanvasEvents() def BuildPopup(self): # build pop-up menu for right-click display self.popup_menu = popup = wx.Menu() MenuItem(self, popup, 'Configure', '', self.configure) MenuItem(self, popup, 'Save Image', '', self.save_figure) popup.AppendSeparator() MenuItem(self, popup, 'Undo Zoom/Pan', '', self.unzoom) MenuItem(self, popup, 'Zoom all the way out', '', self.unzoom_all) popup.AppendSeparator() MenuItem(self, popup, 'Zoom X and Y', '', partial(self.onZoomStyle, style='both x and y'), kind=wx.ITEM_RADIO, checked=True) MenuItem(self, popup, 'Zoom X Only', '', partial(self.onZoomStyle, style='x only'), kind=wx.ITEM_RADIO) MenuItem(self, popup, 'Zoom Y Only', '', partial(self.onZoomStyle, style='y only'), kind=wx.ITEM_RADIO) def onZoomStyle(self, event=None, style='both x and y'): self.conf.zoom_style = style def _updateCanvasDraw(self): """ Overload of the draw function that update axes position before each draw""" fn = self.canvas.draw def draw2(*a,**k): self._updateGridSpec() return fn(*a,**k) self.canvas.draw = draw2 def get_default_margins(self): """get default margins""" trans = self.fig.transFigure.inverted().transform # Static margins l, t, r, b = self.axesmargins (l, b), (r, t) = trans(((l, b), (r, t))) # Extent dl, dt, dr, db = 0, 0, 0, 0 for i, ax in enumerate(self.fig.get_axes()): (x0, y0),(x1, y1) = ax.get_position().get_points() try: (ox0, oy0), (ox1, oy1) = ax.get_tightbbox(self.canvas.get_renderer()).get_points() (ox0, oy0), (ox1, oy1) = trans(((ox0 ,oy0),(ox1 ,oy1))) dl = min(0.2, max(dl, (x0 - ox0))) dt = min(0.2, max(dt, (oy1 - y1))) dr = min(0.2, max(dr, (ox1 - x1))) db = min(0.2, max(db, (y0 - oy0))) except: pass return (l + dl, t + dt, r + dr, b + db) def autoset_margins(self): """auto-set margins left, bottom, right, top according to the specified margins (in pixels) and axes extent (taking into account labels, title, axis) """ if not self.conf.auto_margins: return # coordinates in px -> [0,1] in figure coordinates trans = self.fig.transFigure.inverted().transform # Static margins if not self.use_dates: self.conf.margins = l, t, r, b = self.get_default_margins() self.gridspec.update(left=l, top=1-t, right=1-r, bottom=b) # Axes positions update for ax in self.fig.get_axes(): try: ax.update_params() except ValueError: pass ax.set_position(ax.figbox) def draw(self): self.canvas.draw() def update_line(self, trace, xdata, ydata, side='left', draw=False, update_limits=True): """ update a single trace, for faster redraw """ x = self.conf.get_mpl_line(trace) x.set_data(xdata, ydata) # datarange = [xdata.min(), xdata.max(), ydata.min(), ydata.max()] # self.conf.set_trace_datarange(datarange, trace=trace) axes = self.axes if side == 'right': axes = self.get_right_axes() if update_limits: self.set_viewlimits() if draw: self.draw() def get_figure(self): return self.fig def __onPickEvent(self, event=None): """pick events""" legline = event.artist trace = self.conf.legend_map.get(legline, None) visible = True if trace is not None and self.conf.hidewith_legend: line, legline, legtext = trace visible = not line.get_visible() line.set_visible(visible) if visible: legline.set_zorder(10.00) legline.set_alpha(1.00) legtext.set_zorder(10.00) legtext.set_alpha(1.00) else: legline.set_alpha(0.50) legtext.set_alpha(0.50) #### ## GUI events #### def report_leftdown(self, event=None): if event is None: return ex, ey = event.x, event.y msg = '' try: x, y = self.axes.transData.inverted().transform((ex, ey)) except: x, y = event.xdata, event.ydata if x is not None and y is not None: msg = "X,Y= %g, %g" % (x, y) if len(self.fig.get_axes()) > 1: ax2 = self.fig.get_axes()[1] try: x2, y2 = ax2.transData.inverted().transform((ex, ey)) msg = "X,Y,Y2= %g, %g, %g" % (x, y, y2) except: pass nsbar = getattr(self, 'nstatusbar', 1) self.write_message(msg, panel=max(0, nsbar - 2)) if (self.cursor_callback is not None and hasattr(self.cursor_callback , '__call__')): self.cursor_callback(x=event.xdata, y=event.ydata)
class PhaseModulatorPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, wx.ID_ANY) self.parent = parent self.left_panel = wx.Panel(self, wx.ID_ANY) self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER) # PANELS ======================================================================================================= # LEFT Panel --------------------------------------------------------------------------------------------------- self.text_sample_rate = wx.TextCtrl(self.left_panel, wx.ID_ANY, "80000") self.text_mainlobe_error = wx.TextCtrl(self.left_panel, wx.ID_ANY, "0.06") self.text_carrier_amplitude = wx.TextCtrl(self.left_panel, wx.ID_ANY, "1") self.text_carrier_frequency = wx.TextCtrl(self.left_panel, wx.ID_ANY, "1e3") self.combo_waveform = wx.ComboBox(self.left_panel, wx.ID_ANY, choices=["Sine", "Triangle", 'Sawtooth', "Square", "Shift Keying"], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.combo_modulation = wx.ComboBox(self.left_panel, wx.ID_ANY, choices=["Amplitude", "Frequency", "Phase"], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.text_modulation_index = wx.TextCtrl(self.left_panel, wx.ID_ANY, "1") self.text_message_frequency = wx.TextCtrl(self.left_panel, wx.ID_ANY, "100") self.text_message_phase = wx.TextCtrl(self.left_panel, wx.ID_ANY, "0") self.text_report_rms = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_report_bw = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.btn_start = wx.Button(self.left_panel, wx.ID_ANY, "RUN") self.combo_mode = wx.ComboBox(self.left_panel, wx.ID_ANY, choices=["Single", "Continuous"], style=wx.CB_DROPDOWN) # PLOT Panel --------------------------------------------------------------------------------------------------- self.figure = plt.figure(figsize=(1, 1)) # look into Figure((5, 4), 75) self.canvas = FigureCanvas(self.plot_panel, -1, self.figure) self.toolbar = NavigationToolbar(self.canvas) self.toolbar.Realize() # instance variables ------------------------------------------------------------------------------------------- self.flag_complete = True # Flag indicates any active threads (False) or thread completed (True) self.t = threading.Thread() self.pm = pm(self) self.user_input = {} # Plot objects ------------------------------------------------------------------------------------------------- self.ax1 = self.figure.add_subplot(211) self.ax2 = self.figure.add_subplot(212) self.temporal, = self.ax1.plot([], [], linestyle='-') self.temporal_2, = self.ax1.plot([], [], linestyle='--') self.spectral, = self.ax2.plot([], [], color='#C02942') # Plot Annotations --------------------------------------------------------------------------------------------- # https://stackoverflow.com/a/38677732 self.arrow_dim_obj = self.ax2.annotate("", xy=(0, 0), xytext=(0, 0), textcoords=self.ax2.transData, arrowprops=dict(arrowstyle='<->')) self.bar_dim_obj = self.ax2.annotate("", xy=(0, 0), xytext=(0, 0), textcoords=self.ax2.transData, arrowprops=dict(arrowstyle='|-|')) bbox = dict(fc="white", ec="none") self.dim_text = self.ax2.text(0, 0, "", ha="center", va="center", bbox=bbox) # BINDINGS ===================================================================================================== # Run Measurement (start subprocess) --------------------------------------------------------------------------- on_run_event = lambda event: self.on_run(event) self.Bind(wx.EVT_BUTTON, on_run_event, self.btn_start) on_combo_modulation_select = lambda event: self.combo_modulation_select(event) self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_combo_modulation_select, self.combo_modulation) self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_combo_modulation_select, self.combo_waveform) self.__set_properties() self.__do_layout() self.__do_plot_layout() def __set_properties(self): self.SetBackgroundColour(wx.Colour(255, 255, 255)) self.canvas.SetMinSize((700, 490)) self.left_panel.SetBackgroundColour(wx.Colour(255, 255, 255)) self.plot_panel.SetBackgroundColour(wx.Colour(255, 255, 255)) self.left_panel.SetMinSize((310, 502)) # self.left_sub_panel.SetBackgroundColour(wx.Colour(255, 0, 255)) self.plot_panel.SetMinSize((700, 502)) self.combo_modulation.SetSelection(0) self.combo_waveform.SetSelection(0) self.canvas.SetMinSize((700, 490)) self.combo_mode.SetSelection(0) self.combo_mode.SetMinSize((110, 23)) def __do_layout(self): sizer_2 = wx.GridSizer(1, 1, 0, 0) grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0) grid_sizer_left_panel = wx.GridBagSizer(0, 0) grid_sizer_left_sub_btn_row = wx.GridBagSizer(0, 0) grid_sizer_plot = wx.GridBagSizer(0, 0) # LEFT PANEL =================================================================================================== # TITLE -------------------------------------------------------------------------------------------------------- label_1 = wx.StaticText(self.left_panel, wx.ID_ANY, "MODULATION SCHEMES") label_1.SetFont(wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_1, (0, 0), (1, 2), 0, 0) # static_line_1 = wx.StaticLine(self.left_panel, wx.ID_ANY) # static_line_1.SetMinSize((300, 2)) # grid_sizer_left_panel.Add(static_line_1, (1, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) # SIMULATION SETUP --------------------------------------------------------------------------------------------- label_source = wx.StaticText(self.left_panel, wx.ID_ANY, "Simulation Settings") label_source.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_source, (2, 0), (1, 2), wx.TOP, 10) static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_2.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_2, (3, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) label_sample_rate = wx.StaticText(self.left_panel, wx.ID_ANY, "Sample Rate") grid_sizer_left_panel.Add(label_sample_rate, (4, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_sample_rate, (4, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(Hz)") grid_sizer_left_panel.Add(label_Hz, (4, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) label_error = wx.StaticText(self.left_panel, wx.ID_ANY, "Error") grid_sizer_left_panel.Add(label_error, (5, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_mainlobe_error, (5, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) label_percent = wx.StaticText(self.left_panel, wx.ID_ANY, "(%)") grid_sizer_left_panel.Add(label_percent, (5, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) # CARRIER SETUP ------------------------------------------------------------------------------------------------ label_source = wx.StaticText(self.left_panel, wx.ID_ANY, "Carrier Signal") label_source.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_source, (6, 0), (1, 1), wx.TOP, 10) static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_2.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_2, (7, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) label_carrier_amplitude = wx.StaticText(self.left_panel, wx.ID_ANY, "Amplitude:") grid_sizer_left_panel.Add(label_carrier_amplitude, (8, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_carrier_amplitude, (8, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) label_carrier_frequency = wx.StaticText(self.left_panel, wx.ID_ANY, "Frequency:") grid_sizer_left_panel.Add(label_carrier_frequency, (9, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_carrier_frequency, (9, 1), (1, 1), wx.LEFT, 5) label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(Hz)") grid_sizer_left_panel.Add(label_Hz, (9, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) # MODULATION SETUP --------------------------------------------------------------------------------------------- label_source = wx.StaticText(self.left_panel, wx.ID_ANY, "Message Signal") label_source.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_source, (10, 0), (1, 2), wx.TOP, 10) static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_2.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_2, (11, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) label_modulation = wx.StaticText(self.left_panel, wx.ID_ANY, "Modulation:") grid_sizer_left_panel.Add(label_modulation, (12, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.combo_modulation, (12, 1), (1, 1), wx.EXPAND | wx.BOTTOM | wx.LEFT, 5) label_waveform = wx.StaticText(self.left_panel, wx.ID_ANY, "Waveform:") grid_sizer_left_panel.Add(label_waveform, (13, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.combo_waveform, (13, 1), (1, 1), wx.EXPAND | wx.BOTTOM | wx.LEFT, 5) label_modulation_index = wx.StaticText(self.left_panel, wx.ID_ANY, "Modulation Index:") grid_sizer_left_panel.Add(label_modulation_index, (14, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_modulation_index, (14, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) label_message_frequency = wx.StaticText(self.left_panel, wx.ID_ANY, "Frequency:") grid_sizer_left_panel.Add(label_message_frequency, (15, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_message_frequency, (15, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(Hz)") grid_sizer_left_panel.Add(label_Hz, (15, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5) label_message_phase = wx.StaticText(self.left_panel, wx.ID_ANY, "Phase:") grid_sizer_left_panel.Add(label_message_phase, (16, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_message_phase, (16, 1), (1, 1), wx.LEFT, 5) label_deg = wx.StaticText(self.left_panel, wx.ID_ANY, "(deg)") grid_sizer_left_panel.Add(label_deg, (16, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) # METRICS (RESULTS) -------------------------------------------------------------------------------------------- label_source = wx.StaticText(self.left_panel, wx.ID_ANY, "Metrics") label_source.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_source, (17, 0), (1, 1), wx.TOP, 10) static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_2.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_2, (18, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) label_report_rms = wx.StaticText(self.left_panel, wx.ID_ANY, "RMS:") grid_sizer_left_panel.Add(label_report_rms, (19, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_report_rms, (19, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) label_report_bw = wx.StaticText(self.left_panel, wx.ID_ANY, "BW:") grid_sizer_left_panel.Add(label_report_bw, (20, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5) grid_sizer_left_panel.Add(self.text_report_bw, (20, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5) # BUTTONS ------------------------------------------------------------------------------------------------------ static_line_4 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_4.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_4, (21, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) grid_sizer_left_sub_btn_row.Add(self.btn_start, (0, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) grid_sizer_left_sub_btn_row.Add(self.combo_mode, (0, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) grid_sizer_left_panel.Add(grid_sizer_left_sub_btn_row, (22, 0), (1, 3), wx.ALIGN_TOP | wx.BOTTOM, 13) self.left_panel.SetSizer(grid_sizer_left_panel) # PLOT PANEL =================================================================================================== grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.AddGrowableRow(0) grid_sizer_plot.AddGrowableCol(0) self.plot_panel.SetSizer(grid_sizer_plot) # add to main panel -------------------------------------------------------------------------------------------- grid_sizer_1.Add(self.left_panel, 0, wx.EXPAND | wx.RIGHT, 5) grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5) grid_sizer_1.AddGrowableRow(0) grid_sizer_1.AddGrowableCol(1) sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0) self.SetSizer(sizer_2) self.Layout() # ------------------------------------------------------------------------------------------------------------------ def toggle_controls(self): if self.text_carrier_amplitude.Enabled: self.combo_modulation.Disable() self.text_sample_rate.Disable() self.text_mainlobe_error.Disable() self.text_carrier_amplitude.Disable() self.text_carrier_frequency.Disable() self.text_modulation_index.Disable() self.text_message_frequency.Disable() self.text_message_phase.Disable() else: self.combo_modulation.Enable() self.text_sample_rate.Enable() self.text_mainlobe_error.Enable() self.text_carrier_amplitude.Enable() self.text_carrier_frequency.Enable() self.text_modulation_index.Enable() self.text_message_frequency.Enable() self.text_message_phase.Enable() def combo_modulation_select(self, evt): if self.combo_modulation.GetSelection() == 0: # amplitude modulation self.text_modulation_index.SetValue('1') elif self.combo_modulation.GetSelection() == 1: # frequency modulation self.text_modulation_index.SetValue('5') elif self.combo_modulation.GetSelection() == 2: # phase modulation self.text_modulation_index.SetValue('5') else: raise ValueError("Invalid modulation selected!") # PSK if self.combo_waveform.GetSelection() in (3, 4): if self.combo_modulation.GetSelection() == 2: self.text_message_phase.SetValue('180') else: self.text_message_phase.SetValue('0') else: self.text_message_phase.SetValue('0') # ------------------------------------------------------------------------------------------------------------------ def get_values(self): mode = self.combo_mode.GetSelection() sample_rate = to_float(self.text_sample_rate.GetValue(), property="sample rate") main_lobe_error = to_float(self.text_mainlobe_error.GetValue(), property="main lobe error") modulation_type = self.combo_modulation.GetSelection() waveform_lookup = {0: 'sine', 1: 'triangle', 2: 'sawtooth', 3: 'square', 4: 'keying'} waveform_type = waveform_lookup[self.combo_waveform.GetSelection()] carrier_amplitude = to_float(self.text_carrier_amplitude.GetValue(), property="carrier amplitude") carrier_frequency = to_float(self.text_carrier_frequency.GetValue(), property="carrier frequency") modulation_index = to_float(self.text_modulation_index.GetValue(), property="modulation index") message_frequency = to_float(self.text_message_frequency.GetValue(), property="message frequency") message_phase = to_float(self.text_message_phase.GetValue(), property="message phase") self.user_input = {'mode': mode, 'sample_rate': sample_rate, 'main_lobe_error': main_lobe_error, 'waveform_type': waveform_type, 'modulation_type': modulation_type, 'carrier_amplitude': carrier_amplitude, 'carrier_frequency': carrier_frequency, 'modulation_index': modulation_index, 'message_frequency': message_frequency, 'message_phase': message_phase, } self.pm.user_values_good = True # ------------------------------------------------------------------------------------------------------------------ def thread_this(self, func, arg=()): self.t = threading.Thread(target=func, args=arg, daemon=True) self.t.start() # ------------------------------------------------------------------------------------------------------------------ def on_run(self, evt): self.get_values() if not self.t.is_alive() and self.flag_complete: # start new thread self.thread_this(self.pm.start, (self.user_input,)) self.btn_start.SetLabel('STOP') elif self.t.is_alive() and self.user_input['mode'] == 1: # stop continuous # https://stackoverflow.com/a/36499538 self.t.do_run = False self.btn_start.SetLabel('RUN') else: print('thread already running.') # ------------------------------------------------------------------------------------------------------------------ def __do_plot_layout(self): self.ax1.set_title('SAMPLED TIMED SERIES DATA') self.ax1.set_xlabel('TIME (ms)') self.ax1.set_ylabel('AMPLITUDE') self.ax2.set_title('SPECTRAL DATA') self.ax2.set_xlabel('FREQUENCY (kHz)') self.ax2.set_ylabel('AMPLITUDE (V)') self.ax2.grid() self.figure.align_ylabels([self.ax1, self.ax2]) self.figure.tight_layout() def plot(self, params): # TEMPORAL ----------------------------------------------------------------------------------------------------- xt = params['xt'] yt = params['yt'] mt = params['mt'] self.temporal.set_data(xt, yt) self.temporal_2.set_data(xt, mt) xt_left = params['xt_left'] xt_right = params['xt_right'] yt_btm = params['yt_btm'] yt_top = params['yt_top'] yt_tick = params['yt_tick'] self.ax1.set_xlim(left=xt_left, right=xt_right) # self.ax1.set_yticks(np.arange(yt_btm, yt_top, yt_tick)) # SPECTRAL ----------------------------------------------------------------------------------------------------- xf = params['xf'] yf = params['yf'] self.spectral.set_data(xf, yf) xf_left = params['xf_left'] xf_right = params['xf_right'] yf_btm = params['yf_btm'] yf_top = params['yf_top'] yf_ticks = params['yf_ticks'] self.ax2.set_xlim(left=xf_left, right=xf_right) self.ax2.set_ylim(bottom=yf_btm, top=yf_top) self.ax2.set_yticks(yf_ticks) # Annotations -------------------------------------------------------------------------------------------------- dim_height = params['dim_height'] dim_left = params['dim_left'] dim_right = params['dim_right'] bw = params['bw'] # Arrow dimension line update ---------------------------------------------------------------------------------- # https://stackoverflow.com/a/48684902 ------------------------------------------------------------------------- self.arrow_dim_obj.xy = (dim_left, dim_height) self.arrow_dim_obj.set_position((dim_right, dim_height)) self.arrow_dim_obj.textcoords = self.ax2.transData # Bar dimension line update ------------------------------------------------------------------------------------ self.bar_dim_obj.xy = (dim_left, dim_height) self.bar_dim_obj.set_position((dim_right, dim_height)) self.bar_dim_obj.textcoords = self.ax2.transData # dimension text update ---------------------------------------------------------------------------------------- self.dim_text.set_position((dim_left + (bw / 2), dim_height)) self.dim_text.set_text(params['bw_text']) # REDRAW PLOT -------------------------------------------------------------------------------------------------- self.plot_redraw() def plot_redraw(self): try: self.ax1.relim() # recompute the ax.dataLim except ValueError: xt_length = len(self.ax1.get_xdata()) yt_length = len(self.ax1.get_ydata()) print(f'Are the lengths of xt: {xt_length} and yt: {yt_length} mismatched?') raise self.ax1.margins(x=0) self.ax1.autoscale(axis='y') # UPDATE PLOT FEATURES ----------------------------------------------------------------------------------------- self.figure.tight_layout() self.toolbar.update() # Not sure why this is needed - ADS self.canvas.draw() self.canvas.flush_events() def results_update(self, results): yrms = results['yrms'] bw = results['bw'] self.text_report_rms.SetValue(f"{'{:0.3e}'.format(yrms)}") self.text_report_bw.SetValue(f"{'{:0.3e}'.format(bw)}") def error_dialog(self, error_message): print(error_message) dial = wx.MessageDialog(None, str(error_message), 'Error', wx.OK | wx.ICON_ERROR) dial.ShowModal()
class MyDemoPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, wx.ID_ANY) self.frame = parent self.left_panel = wx.Panel(self, wx.ID_ANY) self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER) # PLOT Panel --------------------------------------------------------------------------------------------------- self.figure = plt.figure(figsize=(1, 1)) # look into Figure((5, 4), 75) self.canvas = FigureCanvas(self.plot_panel, -1, self.figure) self.toolbar = NavigationToolbar(self.canvas) self.toolbar.Realize() self.ax1 = self.figure.add_subplot(211) self.ax2 = self.figure.add_subplot(212) self.temporal, = self.ax1.plot([], [], linestyle='-', linewidth=5, alpha=0.3) self.temporal_sampled, = self.ax1.plot([], [], linestyle='-', marker='x') self.spectral, = self.ax2.plot([], [], color='#C02942') self.x, self.y = [], [] self.text_ctrl_fs = wx.TextCtrl(self.left_panel, wx.ID_ANY, style=wx.TE_PROCESS_ENTER) self.text_ctrl_ldf = wx.TextCtrl(self.left_panel, wx.ID_ANY, style=wx.TE_PROCESS_ENTER) self.text_ctrl_ldf.SetToolTip("Lowest Recoverable Frequency") self.text_ctrl_samples = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_ctrl_rms_true = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_ctrl_rms_sampled = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_ctrl_rms_delta = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_ctrl_cycles = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_ctrl_samples_per_cycle = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_ctrl_aliased_freq01 = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_ctrl_aliased_freq02 = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) self.text_ctrl_aliased_freq03 = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY) on_update = lambda event: self.update(event) self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_fs) self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_ldf) self.__set_properties() self.__do_layout() self.__do_plot_layout() self.update(wx.Event) def __set_properties(self): self.SetBackgroundColour(wx.Colour(240, 240, 240)) self.canvas.SetMinSize((700, 490)) self.text_ctrl_fs.SetValue('12') self.text_ctrl_ldf.SetValue('100') # width = 200 # self.text_ctrl_fs.SetMaxSize((width, 23)) # self.text_ctrl_ldf.SetMaxSize((width, 23)) # # self.text_ctrl_samples.SetMaxSize((width, 23)) # self.text_ctrl_rms_true.SetMaxSize((width, 23)) # self.text_ctrl_rms_sampled.SetMaxSize((width, 23)) # self.text_ctrl_rms_delta.SetMaxSize((width, 23)) # # self.text_ctrl_cycles.SetMaxSize((width, 23)) # self.text_ctrl_samples_per_cycle.SetMaxSize((width, 23)) # self.text_ctrl_aliased_freq01.SetMaxSize((width, 23)) # self.text_ctrl_aliased_freq02.SetMaxSize((width, 23)) # self.text_ctrl_aliased_freq03.SetMaxSize((width, 23)) def __do_layout(self): sizer_2 = wx.GridSizer(1, 1, 0, 0) grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0) grid_sizer_plot = wx.GridBagSizer(0, 0) grid_sizer_left_panel = wx.GridBagSizer(0, 0) # LEFT PANEL --------------------------------------------------------------------------------------------------- # TITLE -------------------------------------------------------------------------------------------------------- row = 0 label_1 = wx.StaticText(self.left_panel, wx.ID_ANY, "RMS && ALIASING") label_1.SetFont( wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(label_1, (row, 0), (1, 3), wx.LEFT | wx.RIGHT | wx.TOP, 5) row += 1 static_line_1 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_1.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_1, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) # SIGNAL ------------------------------------------------------------------------------------------------------- row += 1 lbl_signal = wx.StaticText(self.left_panel, wx.ID_ANY, "Signal Characteristics") lbl_signal.SetFont( wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(lbl_signal, (row, 0), (1, 3), wx.LEFT | wx.RIGHT, 5) row += 1 lbl_signal_rms = wx.StaticText(self.left_panel, wx.ID_ANY, "RMS:") grid_sizer_left_panel.Add(lbl_signal_rms, (row, 0), (1, 1), wx.LEFT | wx.RIGHT, 5) lbl_signal_rms_val = wx.StaticText(self.left_panel, wx.ID_ANY, "1") grid_sizer_left_panel.Add(lbl_signal_rms_val, (row, 1), (1, 2), wx.BOTTOM, 5) row += 1 lbl_signal_freq = wx.StaticText(self.left_panel, wx.ID_ANY, "Frequency (f0):") grid_sizer_left_panel.Add(lbl_signal_freq, (row, 0), (1, 1), wx.LEFT | wx.RIGHT, 5) lbl_signal_freq_val = wx.StaticText(self.left_panel, wx.ID_ANY, "1000 Hz") grid_sizer_left_panel.Add(lbl_signal_freq_val, (row, 1), (1, 2), wx.BOTTOM, 5) # SETTINGS ----------------------------------------------------------------------------------------------------- row += 2 lbl_settings = wx.StaticText(self.left_panel, wx.ID_ANY, "Settings") lbl_settings.SetFont( wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(lbl_settings, (row, 0), (1, 3), wx.LEFT | wx.RIGHT, 5) row += 1 static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_2.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_2, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) row += 1 lbl_fs = wx.StaticText(self.left_panel, wx.ID_ANY, "Fs:") grid_sizer_left_panel.Add( lbl_fs, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_fs, (row, 1), (1, 1), wx.BOTTOM, 5) lbl_units_kHz = wx.StaticText(self.left_panel, wx.ID_ANY, "(kHz):") grid_sizer_left_panel.Add( lbl_units_kHz, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) row += 1 lbl_ldf = wx.StaticText(self.left_panel, wx.ID_ANY, "LDF:") lbl_ldf.SetToolTip("Lowest Recoverable Frequency") grid_sizer_left_panel.Add( lbl_ldf, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_ldf, (row, 1), (1, 1), wx.BOTTOM, 5) lbl_units_hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(Hz):") grid_sizer_left_panel.Add( lbl_units_hz, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) # RESULTS ------------------------------------------------------------------------------------------------------ row += 1 lbl_results = wx.StaticText(self.left_panel, wx.ID_ANY, "Results") lbl_results.SetFont( wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add(lbl_results, (row, 0), (1, 3), wx.LEFT | wx.RIGHT, 5) row += 1 static_line_3 = wx.StaticLine(self.left_panel, wx.ID_ANY) static_line_3.SetMinSize((300, 2)) grid_sizer_left_panel.Add(static_line_3, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5) row += 1 lbl_samples = wx.StaticText(self.left_panel, wx.ID_ANY, "Samples (N):") grid_sizer_left_panel.Add( lbl_samples, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_samples, (row, 1), (1, 2), wx.BOTTOM, 5) row += 1 lbl_cycles = wx.StaticText(self.left_panel, wx.ID_ANY, "cycles:") grid_sizer_left_panel.Add( lbl_cycles, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_cycles, (row, 1), (1, 2), wx.BOTTOM, 5) row += 1 lbl_samples_per_cycle = wx.StaticText(self.left_panel, wx.ID_ANY, "N/cycles:") grid_sizer_left_panel.Add( lbl_samples_per_cycle, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_samples_per_cycle, (row, 1), (1, 2), wx.BOTTOM, 5) row += 2 lbl_rms_true = wx.StaticText(self.left_panel, wx.ID_ANY, "RMS (True):") grid_sizer_left_panel.Add( lbl_rms_true, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_rms_true, (row, 1), (1, 2), wx.BOTTOM, 5) row += 1 lbl_rms_sampled = wx.StaticText(self.left_panel, wx.ID_ANY, "RMS (Sampled):") grid_sizer_left_panel.Add( lbl_rms_sampled, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_rms_sampled, (row, 1), (1, 2), wx.BOTTOM, 5) row += 1 lbl_delta = wx.StaticText(self.left_panel, wx.ID_ANY, "Delta RMS:") grid_sizer_left_panel.Add( lbl_delta, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_rms_delta, (row, 1), (1, 1), wx.BOTTOM, 5) lbl_units_ppm = wx.StaticText(self.left_panel, wx.ID_ANY, "(ppm):") grid_sizer_left_panel.Add( lbl_units_ppm, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) row += 2 lbl_aliased = wx.StaticText(self.left_panel, wx.ID_ANY, "Aliased Frequency:") lbl_aliased.SetFont( wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) grid_sizer_left_panel.Add( lbl_aliased, (row, 0), (1, 3), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) row += 1 lbl_h1 = wx.StaticText(self.left_panel, wx.ID_ANY, "1st Harmonic:") grid_sizer_left_panel.Add( lbl_h1, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_aliased_freq01, (row, 1), (1, 1), wx.BOTTOM, 5) lbl_units_hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(kHz):") grid_sizer_left_panel.Add( lbl_units_hz, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) row += 1 lbl_h2 = wx.StaticText(self.left_panel, wx.ID_ANY, "3rd Harmonic:") grid_sizer_left_panel.Add( lbl_h2, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_aliased_freq02, (row, 1), (1, 1), wx.BOTTOM, 5) lbl_units_hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(kHz):") grid_sizer_left_panel.Add( lbl_units_hz, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) row += 1 lbl_h3 = wx.StaticText(self.left_panel, wx.ID_ANY, "5th Harmonic:") grid_sizer_left_panel.Add( lbl_h3, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) grid_sizer_left_panel.Add(self.text_ctrl_aliased_freq03, (row, 1), (1, 1), wx.BOTTOM, 5) lbl_units_hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(kHz):") grid_sizer_left_panel.Add( lbl_units_hz, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5) self.left_panel.SetSizer(grid_sizer_left_panel) # PLOT PANEL =================================================================================================== grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND) grid_sizer_plot.AddGrowableRow(0) grid_sizer_plot.AddGrowableCol(0) self.plot_panel.SetSizer(grid_sizer_plot) # add to main panel -------------------------------------------------------------------------------------------- grid_sizer_1.Add(self.left_panel, 0, wx.EXPAND | wx.RIGHT, 5) grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5) grid_sizer_1.AddGrowableRow(0) grid_sizer_1.AddGrowableCol(1) sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0) self.SetSizer(sizer_2) self.Layout() def popup_dialog(self, message): print(message) dial = wx.MessageDialog(None, str(message), 'Error', wx.OK | wx.ICON_ERROR) dial.ShowModal() def update(self, evt): try: f0 = 1000 signal_rms = 1 Fs = to_float(self.text_ctrl_fs.GetValue()) * 1e3 LDF = to_float(self.text_ctrl_ldf.GetValue()) # TEMPORAL ------------------------------------------------------------------------------------------------- N = getWindowLength(f0, 200e3, windfunc='blackman', error=LDF, mainlobe_type='absolute') self.x, self.y = ADC(signal_rms, f0, 200e3, N, CONTAINS_HARMONICS, CONTAINS_NOISE) N = getWindowLength(f0, Fs, windfunc='blackman', error=LDF, mainlobe_type='absolute') xdt, ydt = ADC(signal_rms, f0, Fs, N, CONTAINS_HARMONICS, CONTAINS_NOISE) rms_true = round( true_signal(signal_rms, N, CONTAINS_HARMONICS, CONTAINS_NOISE), 8) rms_sampled = round(rms_flat(ydt), 8) rms_delta = round(1e6 * (rms_true - rms_sampled), 2) cycles = N * f0 / Fs samples_per_cycles = N / cycles # ALIASING ------------------------------------------------------------------------------------------------- aliased_freq = [1.0, 3.0, 5.0] for idx, harmonic in enumerate(aliased_freq): # https://ez.analog.com/partnerzone/fidus-partnerzone/m/file-uploads/212 Fn = Fs / 2 nyquist_zone = np.floor(f0 * harmonic / Fn) + 1 if nyquist_zone % 2 == 0: aliased_freq[idx] = (Fn - (f0 * harmonic % Fn)) / 1000 else: aliased_freq[idx] = (f0 * harmonic % Fn) / 1000 # WINDOW --------------------------------------------------------------------------------------------------- w = np.blackman(N) # Calculate amplitude correction factor after windowing ---------------------------------------------------- # https://stackoverflow.com/q/47904399/3382269 amplitude_correction_factor = 1 / np.mean(w) # Calculate the length of the FFT -------------------------------------------------------------------------- if (N % 2) == 0: # for even values of N: FFT length is (N / 2) + 1 fft_length = int(N / 2) + 1 else: # for odd values of N: FFT length is (N + 1) / 2 fft_length = int((N + 2) / 2) # SPECTRAL ------------------------------------------------------------------------------------------------- xf_fft = np.round(np.fft.fftfreq(N, d=1. / Fs), 6) yf_fft = (np.fft.fft(ydt * w) / fft_length) * amplitude_correction_factor yf_rfft = yf_fft[:fft_length] xf_rfft = np.round(np.fft.rfftfreq(N, d=1. / Fs), 6) # one-sided if aliased_freq[0] == 0: fh1 = f0 else: fh1 = 1000 * aliased_freq[0] self.plot(fh1, xdt, ydt, fft_length, xf_rfft, yf_rfft) self.results_update(N, rms_true, rms_sampled, rms_delta, np.floor(cycles), samples_per_cycles, aliased_freq) except ValueError as e: self.popup_dialog(e) # ------------------------------------------------------------------------------------------------------------------ def __do_plot_layout(self): self.ax1.set_title('SAMPLED TIMED SERIES DATA') self.ax1.set_xlabel('TIME (ms)') self.ax1.set_ylabel('AMPLITUDE') self.ax2.set_title('DIGITIZED WAVEFORM SPECTRAL RESPONSE') self.ax2.set_xlabel('FREQUENCY (kHz)') self.ax2.set_ylabel('MAGNITUDE (dB)') self.ax2.grid() self.figure.align_ylabels([self.ax1, self.ax2]) self.figure.tight_layout() def plot(self, f0, xt, yt, fft_length, xf, yf): # TEMPORAL ----------------------------------------------------------------------------------------------------- self.temporal.set_data(self.x * 1000, self.y) self.temporal_sampled.set_data(xt * 1000, yt) xt_left = 0 xt_right = 4 / f0 # 4 periods are displayed by default self.ax1.set_xlim(left=xt_left * 1000, right=xt_right * 1000) # SPECTRAL ----------------------------------------------------------------------------------------------------- if NORMALIZE_FFT: yf_peak = max(abs(yf)) self.spectral.set_data(xf / 1000, 20 * np.log10(np.abs(yf / yf_peak))) self.ax2.set_ylabel('MAGNITUDE (dB)') else: self.spectral.set_data(xf / 1000, np.abs(yf) / np.sqrt(2)) self.ax2.set_ylabel('Amplitude (Vrms)') try: self.ax2.relim() # recompute the ax.dataLim except ValueError: print( f'Are the lengths of xt: {len(xf)} and yt: {len(yf)} mismatched?' ) raise self.ax2.autoscale() xf_left = 0 xf_right = xf[fft_length - 1] self.ax2.set_xlim(left=xf_left / 1000, right=xf_right / 1000) # REDRAW PLOT -------------------------------------------------------------------------------------------------- self.plot_redraw() def plot_redraw(self): try: self.ax1.relim() # recompute the ax.dataLim except ValueError: xt_length = len(self.ax1.get_xdata()) yt_length = len(self.ax1.get_ydata()) print( f'Are the lengths of xt: {xt_length} and yt: {yt_length} mismatched?' ) raise self.ax1.margins(x=0) self.ax1.autoscale(axis='y') # UPDATE PLOT FEATURES ----------------------------------------------------------------------------------------- self.figure.tight_layout() self.toolbar.update() # Not sure why this is needed - ADS self.canvas.draw() self.canvas.flush_events() def results_update(self, N, rms_true, rms_sampled, rms_delta, cycles, samples_per_cycle, aliased_freq): self.text_ctrl_samples.SetLabelText(str(N)) self.text_ctrl_rms_true.SetLabelText(str(rms_true)) self.text_ctrl_rms_sampled.SetLabelText(str(rms_sampled)) self.text_ctrl_rms_delta.SetLabelText(str(rms_delta)) self.text_ctrl_cycles.SetLabelText(str(cycles)) self.text_ctrl_samples_per_cycle.SetLabelText(str(samples_per_cycle)) self.text_ctrl_aliased_freq01.SetLabelText(str(aliased_freq[0])) self.text_ctrl_aliased_freq02.SetLabelText(str(aliased_freq[1])) self.text_ctrl_aliased_freq03.SetLabelText(str(aliased_freq[2]))