plotdialogs.pyΒΆ

This is the event handling script derived from the base dialog script plotdialogsbase.py generated using wxFormBuilder template PlotDialogs.fbp.

The event handling template file allows to override any virtual event handler used in the base script and to define new classes to cater for the needs of the application.

It uses the matplotlib to integrate plots in the dialog panel. For detailed description see: Integrating matplotlib with wxPython Dialogs

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#!/usr/bin/env python

import wx
import matplotlib as mpl
mpl.use('WXAgg')  # switch backend (case insensitive)
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg as NavigationToolbar
from operator import itemgetter, attrgetter
import numpy as np
from dataclasses import dataclass
from nardascripting.base.signalsharkdev import *
from nardascripting.base.usrscriptbase import *
from .plotdialogsbase import PlotSettingsBaseDlg, PlotBaseDlg
from nardascripting.base.measthread import *


class PlotSettingsDlg(PlotSettingsBaseDlg):
    """Settings dialog"""

    def __init__(self, parent, msg: str):
        """Initialization"""
        super().__init__(parent)
        self.lbl_my_label.SetLabelText(msg)

    # Code here ---------------------------------------------------------------------------------------
    # Add more methods here if required
    # If virtual event handlers are used in MySettingsBaseDlg, over-ride them here in the derived class
    # -------------------------------------------------------------------------------------------------

    def on_my_button_clicked(self, event):
        # self.lbl_my_label.SetLabelText('clicked')
        event.Skip()

    def on_cancel(self, event):
        event.Skip()

    def on_ok(self, event):
        event.Skip()


@dataclass
class SpectrumSettings:
    """Class for keeping track of measurement parameters."""
    # General settings
    valid: bool = False
    meas_time:float = 0.0
    comment: str = ''

    # Amplitude settings
    trace_list: [] = None
    trace_3rd_detector: Spec3rdDetectorModes = Spec3rdDetectorModes.UNKNOWN
    trace_infinite: bool = False
    input: int = 1
    atten: float = 0.0
    unit: LevelUnits = LevelUnits.UNKNOWN
    lmax: float = 0.0
    lrange: float = 0.0

    # Frequency settings
    fstart: float = 0.0
    fstep: float = 0.0
    bin_count: int = 0
    rbw:float = 0.0
    rbw_filter_type:RbwFilterTypes = RbwFilterTypes.UNKNOWN

    @property
    def fstop(self):
        """Calculates the stop frequency"""
        return self.fstart + (self.fstep*self.bin_count)

    def get_flist(self, divider:float = 1.0):
        """ Calculates the x-axis frequencies

        :param divider: Factor by which the frequency is divided (e.g. 1.0e6 to represent MHz)
        :type divider: float
        :return:
        """
        retvalue = []
        if self.bin_count > 0 and divider != 0.0:
            freq = self.fstart / divider
            freq_step = self.fstep / divider
            retvalue.append(freq)
            for x in range(self.bin_count-1):
                freq += freq_step
                retvalue.append(freq)
        return retvalue

    @property
    def lmin(self):
        """Calculates the y-axis minimum level"""
        return self.lmax - self.lrange

    def clone(self):
        """Clones the current data class"""
        retvalue = None
        try:
            retvalue = SpectrumSettings()
            retvalue.trace_list = self.trace_list
            retvalue.trace_3rd_detector = self.trace_3rd_detector
            retvalue.trace_infinite = self.trace_infinite
            retvalue.input = self.input
            retvalue.atten = self.atten
            retvalue.unit = self.unit
            retvalue.lmax = self.lmax
            retvalue.lrange = self.lrange

            retvalue.fstart = self.fstart
            retvalue.fstep = self.fstep
            retvalue.bin_count = self.bin_count
            retvalue.rbw = self.rbw
            retvalue.rbw_filter_type = self.rbw_filter_type

            retvalue.meas_time = self.meas_time
            retvalue.comment = self.comment

        except:
            retvalue = None
        return retvalue

    @staticmethod
    def from_device(dev: SignalSharkDev):
        """ Creates a SpectrumSettings object and reads data from device

        :param dev: The SignalShark device
        :type dev: SignalSharkDev
        :return: SpectrumSettings
        :rtype: SpectrumSettings
        """
        retvalue = None
        try:
            retvalue = SpectrumSettings()
            if dev.connect():
                dev.scpi.check_error()
                retvalue.trace_list = dev.scpi.spectrum.get_trace_list()
                retvalue.trace_3rd_detector = dev.scpi.spectrum.get_trace_3rd_detector()
                retvalue.trace_infinite = dev.scpi.spectrum.get_trace_infinite()
                retvalue.input = dev.scpi.sense.get_input()
                retvalue.atten = dev.scpi.sense.get_attenuator()
                retvalue.unit = dev.scpi.display.get_unit()
                retvalue.lmax = dev.scpi.display.get_spectrum_lmax()
                retvalue.lrange = dev.scpi.display.get_spectrum_lrange()

                retvalue.fstart = dev.scpi.spectrum.get_data_frequency_start()
                retvalue.fstep = dev.scpi.spectrum.get_data_frequency_step()
                retvalue.bin_count = dev.scpi.spectrum.get_data_count()
                retvalue.rbw = dev.scpi.spectrum.get_rbw()
                retvalue.rbw_filter_type = dev.scpi.spectrum.get_rbw_filter_type()

                retvalue.meas_time = dev.scpi.spectrum.get_measurement_time()
                retvalue.valid = dev.scpi.check_no_error()
        except:
            retvalue = None
        return retvalue


class TracePlotCanvasPnl(wx.Panel):
    """wxWidget plot panel based on matplotlib plot"""

    def __init__(self, parent, tag: str = ''):
        """ wxWidget plot panel based on matplotlib plot

        :param parent: The parent widget.
        :type parent: wx.Widget
        :param tag: Optional string tag.
        :type tag: str
        """
        super(TracePlotCanvasPnl, self).__init__(parent, size=(450, 360))
        self.tag = tag
        self.figure = Figure()
        self.axes = self.figure.add_subplot(111)
        self.canvas = FigureCanvas(self, -1, self.figure)
        # Workaround for size issue
        self.canvas.SetMinSize(wx.Size(10, 10))
        # self.toolbar = NavigationToolbar(self.canvas)
        # self.toolbar.Realize()
        self.x_values = []
        self.lines = {}
        self.ty = []

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, proportion=1, flag=wx.ALL | wx.GROW)
        # self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
        self.SetSizer(self.sizer)
        self.Fit()
        self.trace_colors = {
            '': '#000000',
            'UNKNOWN': '#000000',
            'RMS': '#00C000',
            'PPk': '#FF0000',
            'MPk': '#0000FF',
            'Avg': '#0000FF',
            'Smp': '#0000FF',
            'MnR': '#003955',
            'AvR': '#FF00FF',
            'MxR': '#553900',
            'MxP': '#AA0000',
            'MnP': '#0000AA',
            'MxA': '#0000AA',
            'MxS': '#0000AA'
        }

    def clear_memory(self):
        """Clears the actual traces"""
        self.lines = {}
        for i, line in enumerate(self.axes.lines):
            self.axes.lines.pop(i)
            line.remove()
        self.x_values = []

    def update_settings(self, spectrum_settings: SpectrumSettings, title:str = ''):
        """

        :param spectrum_settings: Measurement settings of the Spectrum View.
        :type spectrum_settings: SpectrumSettings
        :param title: Title of the plot panel.
        :type title: str
        """
        self.clear_memory()

        self.axes.set_ylabel('Power/{}'.format(spectrum_settings.unit))
        self.axes.set_xlabel('Frequency/MHz')
        self.axes.set_title(title)
        self.axes.set_autoscale_on(False)
        self.axes.set_autoscalex_on(False)
        self.axes.set_autoscaley_on(False)
        self.axes.grid(True)
        self.axes.set_xbound(spectrum_settings.fstart / 1e6, spectrum_settings.fstop / 1e6)
        self.axes.set_ybound(spectrum_settings.lmin, spectrum_settings.lmax)
        self.figure.subplots_adjust(left=0.2, right=0.9, bottom=0.2, top=0.9)

        self.x_values = spectrum_settings.get_flist(1e6)
        # self.ty = [float(self.user_selected_value)] * spectrum_settings.bin_count
        # self.axes.plot(self.x_values, self.ty)

        for trace in spectrum_settings.trace_list:
            trace_name = str(trace)
            y_values = [0.0]*spectrum_settings.bin_count
            self.lines[str(trace)] = self.axes.plot(self.x_values, y_values,
                                                    color=self.trace_colors.get(trace_name, '#000000'),
                                                    label=trace_name)
        self.axes.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left',
                         ncol=4, mode='expand', borderaxespad=0.)


    def update_y_values(self, trace: str, y_values: list):
        """ Updates a trace with new y data.

        :param trace: Trace name
        :type trace: str
        :param y_values: List of level values.
        :type y_values: list of float
        """
        line = self.lines.get(trace)
        if not line:
            return
        line[0].set_ydata(y_values)

    def update_plot(self):
        self.canvas.draw()


class SpectrumPlotDlg(PlotBaseDlg):
    """Custom dialog class for data visualization."""

    def __init__(self, parent, title: str, spectrum_settings: SpectrumSettings):
        """Dialog class for data visualization.

        :param parent: The parent window as wx.Window.
        :param title: The dialog caption.
        :param spectrum_settings: The spectrum settings.
        """
        super().__init__(parent=parent)

        self.init_progress_bar(self.pgb_progress)
        self.init_dlg_buttons(self.sdbs_buttons, wx.OK)
        self.btn_ok.SetLabel('Quit')
        self.SetTitle(title)
        self.spectrum_settings = spectrum_settings
        # self.Maximize(True)

        # Add plot area:
        # ---------------------------------------------------------------------
        self.plt = TracePlotCanvasPnl(self.pnl_plot_container)
        self.plt.update_settings(spectrum_settings)
        self.szr_plot.Add(self.plt, 1, wx.EXPAND | wx.ALL, 5)

    def update_progress(self, evt: ProgressEvent):
        """Receives data from thread and updates progressbar and message text

        :param evt: Event of type ProgressEvent.
        """

        # Handle progress
        self.update_progress_bar(evt, self.pgb_progress)

        # Handle message
        if evt.msg is not None:
            self.lbl_status.SetLabelText(evt.msg)

        # Handle data:
        if evt.data is not None:
            for trace_data in evt.data:
                self.plt.update_y_values(trace_data[0], trace_data[1])
            self.plt.update_plot()

        # Handle buttons:
        if evt.btn_style:
            self.update_btn_style(evt.btn_style)

        # Handle icons:
        if evt.icon_style:
            self.update_icon_style(self.bmp_icon, evt.icon_style)

        # Handle help text:
        if evt.help_text:
            self.help_text = evt.help_text

        self.Layout()
        self.Refresh()
        self.Update()

    # Code here --------------------------------------------------------------------------------------
    # Add more methods here if required
    # If virtual event handlers are used in MyMeasGUIBaseDlg, over-ride them here in the derived class
    # ------------------------------------------------------------------------------------------------


# This is just a python module containing helper classes/functions:
if __name__ == '__main__':
    pass

download example04.zip