Replaced """! with """. Exclamation mark showed in doxygen docs.
[debian/gnuradio] / gr-wxgui / src / python / scope_window.py
1 #
2 # Copyright 2008 Free Software Foundation, Inc.
3 #
4 # This file is part of GNU Radio
5 #
6 # GNU Radio is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3, or (at your option)
9 # any later version.
10 #
11 # GNU Radio is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with GNU Radio; see the file COPYING.  If not, write to
18 # the Free Software Foundation, Inc., 51 Franklin Street,
19 # Boston, MA 02110-1301, USA.
20 #
21
22 ##################################################
23 # Imports
24 ##################################################
25 import plotter
26 import common
27 import wx
28 import numpy
29 import time
30 import pubsub
31 from constants import *
32
33 ##################################################
34 # Constants
35 ##################################################
36 DEFAULT_FRAME_RATE = 30
37 DEFAULT_WIN_SIZE = (600, 300)
38 DEFAULT_V_SCALE = 1000
39 TRIGGER_MODES = (
40         ('Off', 0),
41         ('Neg', -1),
42         ('Pos', +1),
43 )
44 TRIGGER_LEVELS = (
45         ('Auto', None),
46         ('+High', 0.75),
47         ('+Med', 0.5),
48         ('+Low', 0.25),
49         ('Zero', 0.0),
50         ('-Low', -0.25),
51         ('-Med', -0.5),
52         ('-High', -0.75),
53 )
54 CHANNEL_COLOR_SPECS = (
55         (0, 0, 1),
56         (0, 1, 0),
57         (1, 0, 0),
58         (1, 0, 1),
59 )
60 AUTORANGE_UPDATE_RATE = 0.5 #sec
61 MARKER_TYPES = (
62         ('Dot Small', 1.0),
63         ('Dot Medium', 2.0),
64         ('Dot Large', 3.0),
65         ('Line Link', None),
66 )
67 DEFAULT_MARKER_TYPE = None
68
69 ##################################################
70 # Scope window control panel
71 ##################################################
72 class control_panel(wx.Panel):
73         """
74         A control panel with wx widgits to control the plotter and scope block.
75         """
76         def __init__(self, parent):
77                 """
78                 Create a new control panel.
79                 @param parent the wx parent window
80                 """
81                 self.parent = parent
82                 wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
83                 self.control_box = control_box = wx.BoxSizer(wx.VERTICAL)
84                 #trigger options
85                 control_box.AddStretchSpacer()
86                 control_box.Add(common.LabelText(self, 'Trigger Options'), 0, wx.ALIGN_CENTER)
87                 control_box.AddSpacer(2)
88                 #trigger mode
89                 self.trigger_mode_chooser = common.DropDownController(self, 'Mode', TRIGGER_MODES, parent, TRIGGER_MODE_KEY)
90                 control_box.Add(self.trigger_mode_chooser, 0, wx.EXPAND)
91                 #trigger level
92                 self.trigger_level_chooser = common.DropDownController(self, 'Level', TRIGGER_LEVELS, parent, TRIGGER_LEVEL_KEY)
93                 parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_level_chooser.Disable(x==0))
94                 control_box.Add(self.trigger_level_chooser, 0, wx.EXPAND)
95                 #trigger channel
96                 choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)]
97                 self.trigger_channel_chooser = common.DropDownController(self, 'Channel', choices, parent, TRIGGER_CHANNEL_KEY)
98                 parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_channel_chooser.Disable(x==0))
99                 control_box.Add(self.trigger_channel_chooser, 0, wx.EXPAND)
100                 #axes options
101                 SPACING = 15
102                 control_box.AddStretchSpacer()
103                 control_box.Add(common.LabelText(self, 'Axes Options'), 0, wx.ALIGN_CENTER)
104                 control_box.AddSpacer(2)
105                 ##################################################
106                 # Scope Mode Box
107                 ##################################################
108                 self.scope_mode_box = wx.BoxSizer(wx.VERTICAL)
109                 control_box.Add(self.scope_mode_box, 0, wx.EXPAND)
110                 #x axis divs
111                 hbox = wx.BoxSizer(wx.HORIZONTAL)
112                 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
113                 hbox.Add(wx.StaticText(self, -1, ' Secs/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
114                 x_buttons = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs)
115                 hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
116                 hbox.AddSpacer(SPACING)
117                 #y axis divs
118                 hbox = wx.BoxSizer(wx.HORIZONTAL)
119                 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
120                 hbox.Add(wx.StaticText(self, -1, ' Units/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
121                 y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs)
122                 parent.subscribe(AUTORANGE_KEY, y_buttons.Disable)
123                 hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
124                 hbox.AddSpacer(SPACING)
125                 #y axis ref lvl
126                 hbox = wx.BoxSizer(wx.HORIZONTAL)
127                 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
128                 hbox.Add(wx.StaticText(self, -1, ' Y Offset '), 1, wx.ALIGN_CENTER_VERTICAL)
129                 y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off)
130                 parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable)
131                 hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
132                 hbox.AddSpacer(SPACING)
133                 ##################################################
134                 # XY Mode Box
135                 ##################################################
136                 self.xy_mode_box = wx.BoxSizer(wx.VERTICAL)
137                 control_box.Add(self.xy_mode_box, 0, wx.EXPAND)
138                 #x and y channel
139                 CHOOSER_WIDTH = 60
140                 CENTER_SPACING = 10
141                 hbox = wx.BoxSizer(wx.HORIZONTAL)
142                 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
143                 choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)]
144                 self.channel_x_chooser = common.DropDownController(self, 'X Ch', choices, parent, SCOPE_X_CHANNEL_KEY, (CHOOSER_WIDTH, -1))
145                 hbox.Add(self.channel_x_chooser, 0, wx.EXPAND)
146                 hbox.AddSpacer(CENTER_SPACING)
147                 self.channel_y_chooser = common.DropDownController(self, 'Y Ch', choices, parent, SCOPE_Y_CHANNEL_KEY, (CHOOSER_WIDTH, -1))
148                 hbox.Add(self.channel_y_chooser, 0, wx.EXPAND)
149                 #div controls
150                 hbox = wx.BoxSizer(wx.HORIZONTAL)
151                 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
152                 hbox.Add(wx.StaticText(self, -1, ' X/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
153                 x_buttons = common.IncrDecrButtons(self, self._on_incr_x_divs, self._on_decr_x_divs)
154                 parent.subscribe(AUTORANGE_KEY, x_buttons.Disable)
155                 hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
156                 hbox.AddSpacer(CENTER_SPACING)
157                 hbox.Add(wx.StaticText(self, -1, ' Y/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
158                 y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs)
159                 parent.subscribe(AUTORANGE_KEY, y_buttons.Disable)
160                 hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
161                 #offset controls
162                 hbox = wx.BoxSizer(wx.HORIZONTAL)
163                 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
164                 hbox.Add(wx.StaticText(self, -1, ' X Off '), 1, wx.ALIGN_CENTER_VERTICAL)
165                 x_off_buttons = common.IncrDecrButtons(self, self._on_incr_x_off, self._on_decr_x_off)
166                 parent.subscribe(AUTORANGE_KEY, x_off_buttons.Disable)
167                 hbox.Add(x_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
168                 hbox.AddSpacer(CENTER_SPACING)
169                 hbox.Add(wx.StaticText(self, -1, ' Y Off '), 1, wx.ALIGN_CENTER_VERTICAL)
170                 y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off)
171                 parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable)
172                 hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
173                 ##################################################
174                 # End Special Boxes
175                 ##################################################
176                 #misc options
177                 control_box.AddStretchSpacer()
178                 control_box.Add(common.LabelText(self, 'Range Options'), 0, wx.ALIGN_CENTER)
179                 #ac couple check box
180                 self.ac_couple_check_box = common.CheckBoxController(self, 'AC Couple', parent, AC_COUPLE_KEY)
181                 control_box.Add(self.ac_couple_check_box, 0, wx.ALIGN_LEFT)
182                 #autorange check box
183                 self.autorange_check_box = common.CheckBoxController(self, 'Autorange', parent, AUTORANGE_KEY)
184                 control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT)
185                 #marker
186                 control_box.AddStretchSpacer()
187                 self.marker_chooser = common.DropDownController(self, 'Marker', MARKER_TYPES, parent, MARKER_KEY)
188                 control_box.Add(self.marker_chooser, 0, wx.EXPAND)
189                 #xy mode
190                 control_box.AddStretchSpacer()
191                 self.scope_xy_mode_button = common.ToggleButtonController(self, parent, SCOPE_XY_MODE_KEY, 'Scope Mode', 'X:Y Mode')
192                 parent.subscribe(SCOPE_XY_MODE_KEY, self._on_scope_xy_mode)
193                 control_box.Add(self.scope_xy_mode_button, 0, wx.EXPAND)
194                 #run/stop
195                 self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run')
196                 control_box.Add(self.run_button, 0, wx.EXPAND)
197                 #set sizer
198                 self.SetSizerAndFit(control_box)
199
200         ##################################################
201         # Event handlers
202         ##################################################
203         def _on_scope_xy_mode(self, mode):
204                 self.scope_mode_box.ShowItems(not mode)
205                 self.xy_mode_box.ShowItems(mode)
206                 self.control_box.Layout()
207         #incr/decr divs
208         def _on_incr_t_divs(self, event):
209                 self.parent.set_t_per_div(
210                         common.get_clean_incr(self.parent[T_PER_DIV_KEY]))
211         def _on_decr_t_divs(self, event):
212                 self.parent.set_t_per_div(
213                         common.get_clean_decr(self.parent[T_PER_DIV_KEY]))
214         def _on_incr_x_divs(self, event):
215                 self.parent.set_x_per_div(
216                         common.get_clean_incr(self.parent[X_PER_DIV_KEY]))
217         def _on_decr_x_divs(self, event):
218                 self.parent.set_x_per_div(
219                         common.get_clean_decr(self.parent[X_PER_DIV_KEY]))
220         def _on_incr_y_divs(self, event):
221                 self.parent.set_y_per_div(
222                         common.get_clean_incr(self.parent[Y_PER_DIV_KEY]))
223         def _on_decr_y_divs(self, event):
224                 self.parent.set_y_per_div(
225                         common.get_clean_decr(self.parent[Y_PER_DIV_KEY]))
226         #incr/decr offset
227         def _on_incr_t_off(self, event):
228                 self.parent.set_t_off(
229                         self.parent[T_OFF_KEY] + self.parent[T_PER_DIV_KEY])
230         def _on_decr_t_off(self, event):
231                 self.parent.set_t_off(
232                         self.parent[T_OFF_KEY] - self.parent[T_PER_DIV_KEY])
233         def _on_incr_x_off(self, event):
234                 self.parent.set_x_off(
235                         self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY])
236         def _on_decr_x_off(self, event):
237                 self.parent.set_x_off(
238                         self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY])
239         def _on_incr_y_off(self, event):
240                 self.parent.set_y_off(
241                         self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY])
242         def _on_decr_y_off(self, event):
243                 self.parent.set_y_off(
244                         self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY])
245
246 ##################################################
247 # Scope window with plotter and control panel
248 ##################################################
249 class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter):
250         def __init__(
251                 self,
252                 parent,
253                 controller,
254                 size,
255                 title,
256                 frame_rate,
257                 num_inputs,
258                 sample_rate_key,
259                 t_scale,
260                 v_scale,
261                 ac_couple,
262                 xy_mode,
263                 scope_trigger_level_key,
264                 scope_trigger_mode_key,
265                 scope_trigger_channel_key,
266                 msg_key,
267         ):
268                 pubsub.pubsub.__init__(self)
269                 #check num inputs
270                 assert num_inputs <= len(CHANNEL_COLOR_SPECS)
271                 #setup
272                 self.sampleses = None
273                 self.ext_controller = controller
274                 self.num_inputs = num_inputs
275                 self.sample_rate_key = sample_rate_key
276                 autorange = v_scale is None
277                 self.autorange_ts = 0
278                 if v_scale is None: v_scale = 1
279                 self.frame_rate_ts = 0
280                 self._init = False #HACK
281                 #scope keys
282                 self.scope_trigger_level_key = scope_trigger_level_key
283                 self.scope_trigger_mode_key = scope_trigger_mode_key
284                 self.scope_trigger_channel_key = scope_trigger_channel_key
285                 #init panel and plot
286                 wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
287                 self.plotter = plotter.channel_plotter(self)
288                 self.plotter.SetSize(wx.Size(*size))
289                 self.plotter.set_title(title)
290                 self.plotter.enable_legend(True)
291                 self.plotter.enable_point_label(True)
292                 #setup the box with plot and controls
293                 self.control_panel = control_panel(self)
294                 main_box = wx.BoxSizer(wx.HORIZONTAL)
295                 main_box.Add(self.plotter, 1, wx.EXPAND)
296                 main_box.Add(self.control_panel, 0, wx.EXPAND)
297                 self.SetSizerAndFit(main_box)
298                 #initial setup
299                 self._register_set_prop(self, RUNNING_KEY, True)
300                 self._register_set_prop(self, AC_COUPLE_KEY, ac_couple)
301                 self._register_set_prop(self, SCOPE_XY_MODE_KEY, xy_mode)
302                 self._register_set_prop(self, AUTORANGE_KEY, autorange)
303                 self._register_set_prop(self, T_PER_DIV_KEY, t_scale)
304                 self._register_set_prop(self, X_PER_DIV_KEY, v_scale)
305                 self._register_set_prop(self, Y_PER_DIV_KEY, v_scale)
306                 self._register_set_prop(self, T_OFF_KEY, 0)
307                 self._register_set_prop(self, X_OFF_KEY, 0)
308                 self._register_set_prop(self, Y_OFF_KEY, 0)
309                 self._register_set_prop(self, T_DIVS_KEY, 8)
310                 self._register_set_prop(self, X_DIVS_KEY, 8)
311                 self._register_set_prop(self, Y_DIVS_KEY, 8)
312                 self._register_set_prop(self, SCOPE_X_CHANNEL_KEY, 0)
313                 self._register_set_prop(self, SCOPE_Y_CHANNEL_KEY, num_inputs-1)
314                 self._register_set_prop(self, FRAME_RATE_KEY, frame_rate)
315                 self._register_set_prop(self, TRIGGER_CHANNEL_KEY, 0)
316                 self._register_set_prop(self, TRIGGER_MODE_KEY, 1)
317                 self._register_set_prop(self, TRIGGER_LEVEL_KEY, None)
318                 self._register_set_prop(self, MARKER_KEY, DEFAULT_MARKER_TYPE)
319                 #register events
320                 self.ext_controller.subscribe(msg_key, self.handle_msg)
321                 for key in (
322                         T_PER_DIV_KEY, X_PER_DIV_KEY, Y_PER_DIV_KEY,
323                         T_OFF_KEY, X_OFF_KEY, Y_OFF_KEY,
324                         T_DIVS_KEY, X_DIVS_KEY, Y_DIVS_KEY,
325                         SCOPE_XY_MODE_KEY,
326                         SCOPE_X_CHANNEL_KEY,
327                         SCOPE_Y_CHANNEL_KEY,
328                         AUTORANGE_KEY,
329                         AC_COUPLE_KEY,
330                         MARKER_KEY,
331                 ): self.subscribe(key, self.update_grid)
332                 #initial update, dont do this here, wait for handle_msg #HACK
333                 #self.update_grid()
334
335         def handle_msg(self, msg):
336                 """
337                 Handle the message from the scope sink message queue.
338                 Plot the list of arrays of samples onto the grid.
339                 Each samples array gets its own channel.
340                 @param msg the time domain data as a character array
341                 """
342                 if not self[RUNNING_KEY]: return
343                 #check time elapsed
344                 if time.time() - self.frame_rate_ts < 1.0/self[FRAME_RATE_KEY]: return
345                 #convert to floating point numbers
346                 samples = numpy.fromstring(msg, numpy.float32)
347                 samps_per_ch = len(samples)/self.num_inputs
348                 self.sampleses = [samples[samps_per_ch*i:samps_per_ch*(i+1)] for i in range(self.num_inputs)]
349                 if not self._init: #HACK
350                         self._init = True
351                         self.update_grid()
352                 #handle samples
353                 self.handle_samples()
354                 self.frame_rate_ts = time.time()
355
356         def handle_samples(self):
357                 """
358                 Handle the cached samples from the scope input.
359                 Perform ac coupling, triggering, and auto ranging.
360                 """
361                 if not self.sampleses: return
362                 sampleses = self.sampleses
363                 #trigger level (must do before ac coupling)
364                 self.ext_controller[self.scope_trigger_channel_key] = self[TRIGGER_CHANNEL_KEY]
365                 self.ext_controller[self.scope_trigger_mode_key] = self[TRIGGER_MODE_KEY]
366                 trigger_level = self[TRIGGER_LEVEL_KEY]
367                 if trigger_level is None: self.ext_controller[self.scope_trigger_level_key] = ''
368                 else:
369                         samples = sampleses[self[TRIGGER_CHANNEL_KEY]]
370                         self.ext_controller[self.scope_trigger_level_key] = \
371                         trigger_level*(numpy.max(samples)-numpy.min(samples))/2 + numpy.average(samples)
372                 #ac coupling
373                 if self[AC_COUPLE_KEY]:
374                         sampleses = [samples - numpy.average(samples) for samples in sampleses]
375                 if self[SCOPE_XY_MODE_KEY]:
376                         x_samples = sampleses[self[SCOPE_X_CHANNEL_KEY]]
377                         y_samples = sampleses[self[SCOPE_Y_CHANNEL_KEY]]
378                         #autorange
379                         if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
380                                 x_min, x_max = common.get_min_max(x_samples)
381                                 y_min, y_max = common.get_min_max(y_samples)
382                                 #adjust the x per div
383                                 x_per_div = common.get_clean_num((x_max-x_min)/self[X_DIVS_KEY])
384                                 if x_per_div != self[X_PER_DIV_KEY]: self.set_x_per_div(x_per_div)
385                                 #adjust the x offset
386                                 x_off = x_per_div*round((x_max+x_min)/2/x_per_div)
387                                 if x_off != self[X_OFF_KEY]: self.set_x_off(x_off)
388                                 #adjust the y per div
389                                 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
390                                 if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div)
391                                 #adjust the y offset
392                                 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
393                                 if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off)
394                                 self.autorange_ts = time.time()
395                         #plot xy channel
396                         self.plotter.set_waveform(
397                                 channel='XY',
398                                 samples=(x_samples, y_samples),
399                                 color_spec=CHANNEL_COLOR_SPECS[0],
400                                 marker=self[MARKER_KEY],
401                         )
402                         #turn off each waveform
403                         for i, samples in enumerate(sampleses):
404                                 self.plotter.set_waveform(
405                                         channel='Ch%d'%(i+1),
406                                         samples=[],
407                                         color_spec=CHANNEL_COLOR_SPECS[i],
408                                 )
409                 else:
410                         #autorange
411                         if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
412                                 bounds = [common.get_min_max(samples) for samples in sampleses]
413                                 y_min = numpy.min(*[bound[0] for bound in bounds])
414                                 y_max = numpy.max(*[bound[1] for bound in bounds])
415                                 #adjust the y per div
416                                 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
417                                 if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div)
418                                 #adjust the y offset
419                                 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
420                                 if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off)
421                                 self.autorange_ts = time.time()
422                         #plot each waveform
423                         for i, samples in enumerate(sampleses):
424                                 #number of samples to scale to the screen
425                                 num_samps = int(self[T_PER_DIV_KEY]*self[T_DIVS_KEY]*self.ext_controller[self.sample_rate_key])
426                                 #handle num samps out of bounds
427                                 if num_samps > len(samples):
428                                         self.set_t_per_div(
429                                                 common.get_clean_decr(self[T_PER_DIV_KEY]))
430                                 elif num_samps < 2:
431                                         self.set_t_per_div(
432                                                 common.get_clean_incr(self[T_PER_DIV_KEY]))
433                                         num_samps = 0
434                                 else:
435                                         #plot samples
436                                         self.plotter.set_waveform(
437                                                 channel='Ch%d'%(i+1),
438                                                 samples=samples[:num_samps],
439                                                 color_spec=CHANNEL_COLOR_SPECS[i],
440                                                 marker=self[MARKER_KEY],
441                                         )
442                         #turn XY channel off
443                         self.plotter.set_waveform(
444                                 channel='XY',
445                                 samples=[],
446                                 color_spec=CHANNEL_COLOR_SPECS[0],
447                         )
448                 #update the plotter
449                 self.plotter.update()
450
451         def update_grid(self, *args):
452                 """
453                 Update the grid to reflect the current settings:
454                 xy divisions, xy offset, xy mode setting
455                 """
456                 #grid parameters
457                 t_per_div = self[T_PER_DIV_KEY]
458                 x_per_div = self[X_PER_DIV_KEY]
459                 y_per_div = self[Y_PER_DIV_KEY]
460                 t_off = self[T_OFF_KEY]
461                 x_off = self[X_OFF_KEY]
462                 y_off = self[Y_OFF_KEY]
463                 t_divs = self[T_DIVS_KEY]
464                 x_divs = self[X_DIVS_KEY]
465                 y_divs = self[Y_DIVS_KEY]
466                 if self[SCOPE_XY_MODE_KEY]:
467                         #update the x axis
468                         self.plotter.set_x_label('Ch%d'%(self[SCOPE_X_CHANNEL_KEY]+1))
469                         self.plotter.set_x_grid(
470                                 -1*x_per_div*x_divs/2.0 + x_off,
471                                 x_per_div*x_divs/2.0 + x_off,
472                                 x_per_div,
473                         )
474                         #update the y axis
475                         self.plotter.set_y_label('Ch%d'%(self[SCOPE_Y_CHANNEL_KEY]+1))
476                         self.plotter.set_y_grid(
477                                 -1*y_per_div*y_divs/2.0 + y_off,
478                                 y_per_div*y_divs/2.0 + y_off,
479                                 y_per_div,
480                         )
481                 else:
482                         #update the t axis
483                         coeff, exp, prefix = common.get_si_components(t_per_div*t_divs + t_off)
484                         self.plotter.set_x_label('Time', prefix+'s')
485                         self.plotter.set_x_grid(
486                                 t_off,
487                                 t_per_div*t_divs + t_off,
488                                 t_per_div,
489                                 10**(-exp),
490                         )
491                         #update the y axis
492                         self.plotter.set_y_label('Counts')
493                         self.plotter.set_y_grid(
494                                 -1*y_per_div*y_divs/2.0 + y_off,
495                                 y_per_div*y_divs/2.0 + y_off,
496                                 y_per_div,
497                         )
498                 #redraw current sample
499                 self.handle_samples()