enode-host 0.1.0__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- enode_host/async_socket.py +42 -20
- enode_host/cli.py +132 -0
- enode_host/constants.py +1 -0
- enode_host/framed_mesh.py +9 -2
- enode_host/gui_framed.py +11 -2
- enode_host/model.py +161 -9
- enode_host/protocol.py +11 -1
- enode_host/view.py +467 -99
- {enode_host-0.1.0.dist-info → enode_host-0.1.3.dist-info}/METADATA +7 -1
- {enode_host-0.1.0.dist-info → enode_host-0.1.3.dist-info}/RECORD +13 -13
- {enode_host-0.1.0.dist-info → enode_host-0.1.3.dist-info}/WHEEL +0 -0
- {enode_host-0.1.0.dist-info → enode_host-0.1.3.dist-info}/entry_points.txt +0 -0
- {enode_host-0.1.0.dist-info → enode_host-0.1.3.dist-info}/top_level.txt +0 -0
enode_host/view.py
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
## PLEASE DO *NOT* EDIT THIS FILE!
|
|
14
14
|
###########################################################################
|
|
15
15
|
|
|
16
|
+
import os
|
|
16
17
|
import wx
|
|
17
18
|
import wx.xrc
|
|
18
19
|
import wx.grid
|
|
@@ -28,9 +29,20 @@ from matplotlib.backends.backend_wxagg import \
|
|
|
28
29
|
NavigationToolbar2WxAgg as NavigationToolbar
|
|
29
30
|
from matplotlib.figure import Figure
|
|
30
31
|
import matplotlib.dates as mdates
|
|
32
|
+
import numpy as np
|
|
31
33
|
import wx.grid as gridlib
|
|
34
|
+
def _env_truthy(name: str) -> bool:
|
|
35
|
+
value = os.environ.get(name)
|
|
36
|
+
if not value:
|
|
37
|
+
return False
|
|
38
|
+
return value.strip().lower() in {"1", "true", "yes", "y", "on"}
|
|
39
|
+
|
|
40
|
+
|
|
32
41
|
try:
|
|
33
|
-
|
|
42
|
+
if _env_truthy("ENODE_DISABLE_WEBVIEW"):
|
|
43
|
+
webview = None
|
|
44
|
+
else:
|
|
45
|
+
import wx.html2 as webview
|
|
34
46
|
except ImportError:
|
|
35
47
|
webview = None
|
|
36
48
|
try:
|
|
@@ -52,6 +64,7 @@ try:
|
|
|
52
64
|
PPS_AGE_LIMIT_SEC,
|
|
53
65
|
PPS_AGE_UPDATE_MS,
|
|
54
66
|
PPS_COL,
|
|
67
|
+
BAT_COL,
|
|
55
68
|
)
|
|
56
69
|
except ImportError:
|
|
57
70
|
from constants import (
|
|
@@ -62,6 +75,7 @@ except ImportError:
|
|
|
62
75
|
PPS_AGE_LIMIT_SEC,
|
|
63
76
|
PPS_AGE_UPDATE_MS,
|
|
64
77
|
PPS_COL,
|
|
78
|
+
BAT_COL,
|
|
65
79
|
)
|
|
66
80
|
import pandas as pd
|
|
67
81
|
|
|
@@ -147,6 +161,8 @@ class TableUpdater:
|
|
|
147
161
|
self.grid.SetColSize(self.col_index[RSSI_COL], 40)
|
|
148
162
|
self.grid.SetColSize(self.col_index["Children"], 70)
|
|
149
163
|
self.grid.SetColSize(self.col_index[PPS_COL], 40)
|
|
164
|
+
if BAT_COL in self.col_index:
|
|
165
|
+
self.grid.SetColSize(self.col_index[BAT_COL], 50)
|
|
150
166
|
self.grid.SetColSize(self.col_index["CMD"], 200)
|
|
151
167
|
for hidden in [
|
|
152
168
|
"PPS-time",
|
|
@@ -226,6 +242,7 @@ class PlotUpdater:
|
|
|
226
242
|
self.timehistory_lines = {}
|
|
227
243
|
self.psd_lines = {}
|
|
228
244
|
self._psd_legend_labels = {}
|
|
245
|
+
self.psd_auto_x = True
|
|
229
246
|
|
|
230
247
|
def init_plot(self):
|
|
231
248
|
self.timehistory_lines = {}
|
|
@@ -247,7 +264,8 @@ class PlotUpdater:
|
|
|
247
264
|
self.axes1.set_xlabel('Time')
|
|
248
265
|
self.axes1.set_ylabel('Acceleration (g)')
|
|
249
266
|
self.axes1.grid(True)
|
|
250
|
-
self.
|
|
267
|
+
if not self.view.time_y_auto:
|
|
268
|
+
self.axes1.set_ylim(*self.view.time_y_limits)
|
|
251
269
|
self.axes1.tick_params(axis='x', labelbottom=True, pad=2)
|
|
252
270
|
self.axes1.legend()
|
|
253
271
|
self.view.m_fig.figure.autofmt_xdate(rotation=0, ha='right')
|
|
@@ -265,11 +283,16 @@ class PlotUpdater:
|
|
|
265
283
|
self.axes2.set_xlabel('Frequency (Hz)')
|
|
266
284
|
self.axes2.set_ylabel('ASD (g/sq(Hz))')
|
|
267
285
|
self.axes2.grid(True)
|
|
268
|
-
self.
|
|
286
|
+
if not self.view.psd_y_auto:
|
|
287
|
+
self.axes2.set_ylim(*self.view.psd_y_limits)
|
|
269
288
|
self.axes2.set_xlim(0, 25)
|
|
289
|
+
self.psd_auto_x = True
|
|
290
|
+
self.axes2.callbacks.connect('xlim_changed', self._on_psd_xlim_changed)
|
|
270
291
|
self.axes2.legend()
|
|
271
292
|
|
|
272
293
|
def figure_update(self):
|
|
294
|
+
min_time_y = None
|
|
295
|
+
max_time_y = None
|
|
273
296
|
for _, row in self.model.mesh_status_data.iterrows():
|
|
274
297
|
node_id = row['nodeID']
|
|
275
298
|
for idx, ch in enumerate(['X', 'Y', 'Z']):
|
|
@@ -278,30 +301,74 @@ class PlotUpdater:
|
|
|
278
301
|
with self.model.plot_mutex[node_id]:
|
|
279
302
|
if len(self.model.timehistory_xdata[node_id]) > 1:
|
|
280
303
|
self.timehistory_lines[node_id_ch].set_xdata(self.model.timehistory_xdata[node_id])
|
|
281
|
-
self.
|
|
304
|
+
series = self.model.timehistory_ydata[node_id][:, idx]
|
|
305
|
+
self.timehistory_lines[node_id_ch].set_ydata(series)
|
|
306
|
+
if self.view.time_y_auto and series is not None and len(series) > 0:
|
|
307
|
+
finite = series[np.isfinite(series)]
|
|
308
|
+
if finite.size:
|
|
309
|
+
smin = float(finite.min())
|
|
310
|
+
smax = float(finite.max())
|
|
311
|
+
min_time_y = smin if min_time_y is None else min(min_time_y, smin)
|
|
312
|
+
max_time_y = smax if max_time_y is None else max(max_time_y, smax)
|
|
282
313
|
if self.model.timehistory_xlim:
|
|
283
314
|
self.axes1.set_xlim(self.model.timehistory_xlim)
|
|
315
|
+
if self.view.time_y_auto and min_time_y is not None and max_time_y is not None:
|
|
316
|
+
if min_time_y == max_time_y:
|
|
317
|
+
pad = 1.0 if min_time_y == 0 else abs(min_time_y) * 0.1
|
|
318
|
+
else:
|
|
319
|
+
pad = (max_time_y - min_time_y) * 0.05
|
|
320
|
+
self.axes1.set_ylim(min_time_y - pad, max_time_y + pad)
|
|
284
321
|
try:
|
|
285
322
|
self.view.m_fig.figure.canvas.draw()
|
|
286
323
|
except Exception:
|
|
287
324
|
raise
|
|
288
325
|
|
|
326
|
+
max_f = None
|
|
327
|
+
min_psd_y = None
|
|
328
|
+
max_psd_y = None
|
|
289
329
|
for _, row in self.model.mesh_status_data.iterrows():
|
|
290
330
|
node_id = row['nodeID']
|
|
331
|
+
f = self.model.psd_xdata.get(node_id)
|
|
291
332
|
for idx, ch in enumerate(['X', 'Y', 'Z']):
|
|
292
333
|
node_id_ch = row['Node ID'] + ch
|
|
293
334
|
if node_id_ch in self.psd_lines and node_id in self.model.psd_ydata:
|
|
294
335
|
with self.model.plot_mutex[node_id]:
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
336
|
+
if f is not None and len(f) > 0:
|
|
337
|
+
self.psd_lines[node_id_ch].set_xdata(f)
|
|
338
|
+
last_f = f[-1]
|
|
339
|
+
if max_f is None or last_f > max_f:
|
|
340
|
+
max_f = last_f
|
|
341
|
+
series = self.model.psd_ydata[node_id][:, idx]
|
|
342
|
+
self.psd_lines[node_id_ch].set_ydata(series)
|
|
343
|
+
if self.view.psd_y_auto and series is not None and len(series) > 0:
|
|
344
|
+
finite = series[np.isfinite(series)]
|
|
345
|
+
finite = finite[finite > 0]
|
|
346
|
+
if finite.size:
|
|
347
|
+
smin = float(finite.min())
|
|
348
|
+
smax = float(finite.max())
|
|
349
|
+
min_psd_y = smin if min_psd_y is None else min(min_psd_y, smin)
|
|
350
|
+
max_psd_y = smax if max_psd_y is None else max(max_psd_y, smax)
|
|
351
|
+
|
|
352
|
+
if self.psd_auto_x and max_f is not None:
|
|
353
|
+
self.axes2.set_xlim(0, round(max_f))
|
|
354
|
+
if self.view.psd_y_auto and min_psd_y is not None and max_psd_y is not None:
|
|
355
|
+
low = min_psd_y * 0.8
|
|
356
|
+
high = max_psd_y * 1.2
|
|
357
|
+
if low <= 0:
|
|
358
|
+
low = min_psd_y * 0.5
|
|
359
|
+
if low > 0 and high > low:
|
|
360
|
+
self.axes2.set_ylim(low, high)
|
|
300
361
|
try:
|
|
301
362
|
self.view.m_fig.figure.canvas.draw()
|
|
302
363
|
except Exception:
|
|
303
364
|
raise
|
|
304
365
|
|
|
366
|
+
def _on_psd_xlim_changed(self, _axes):
|
|
367
|
+
# Disable auto-scaling after user zoom/pan.
|
|
368
|
+
if not self.psd_auto_x:
|
|
369
|
+
return
|
|
370
|
+
self.psd_auto_x = False
|
|
371
|
+
|
|
305
372
|
def update_psd_legend(self):
|
|
306
373
|
if not self.psd_lines:
|
|
307
374
|
return
|
|
@@ -348,6 +415,7 @@ class MergedPlotUpdater:
|
|
|
348
415
|
self.axes2 = view.axes2_merged
|
|
349
416
|
self.timehistory_lines = {}
|
|
350
417
|
self.psd_lines = {}
|
|
418
|
+
self.psd_auto_x = True
|
|
351
419
|
|
|
352
420
|
def _node_label(self, node_id):
|
|
353
421
|
return str(node_id).upper()
|
|
@@ -370,7 +438,8 @@ class MergedPlotUpdater:
|
|
|
370
438
|
self.axes1.set_xlabel('Time')
|
|
371
439
|
self.axes1.set_ylabel('Acceleration (g)')
|
|
372
440
|
self.axes1.grid(True)
|
|
373
|
-
self.
|
|
441
|
+
if not self.view.time_y_auto:
|
|
442
|
+
self.axes1.set_ylim(*self.view.time_y_limits)
|
|
374
443
|
self.axes1.tick_params(axis='x', labelbottom=True, pad=2)
|
|
375
444
|
self.axes1.legend()
|
|
376
445
|
self.view.m_fig_merged.figure.autofmt_xdate(rotation=0, ha='right')
|
|
@@ -387,11 +456,16 @@ class MergedPlotUpdater:
|
|
|
387
456
|
self.axes2.set_xlabel('Frequency (Hz)')
|
|
388
457
|
self.axes2.set_ylabel('ASD (g/sq(Hz))')
|
|
389
458
|
self.axes2.grid(True)
|
|
390
|
-
self.
|
|
459
|
+
if not self.view.psd_y_auto:
|
|
460
|
+
self.axes2.set_ylim(*self.view.psd_y_limits)
|
|
391
461
|
self.axes2.set_xlim(0, 25)
|
|
462
|
+
self.psd_auto_x = True
|
|
463
|
+
self.axes2.callbacks.connect('xlim_changed', self._on_psd_xlim_changed)
|
|
392
464
|
self.axes2.legend()
|
|
393
465
|
|
|
394
466
|
def figure_update(self):
|
|
467
|
+
min_time_y = None
|
|
468
|
+
max_time_y = None
|
|
395
469
|
for node_id in self.model.merged_node_ids:
|
|
396
470
|
node_label = self._node_label(node_id)
|
|
397
471
|
xdata = self.model.merged_timehistory_xdata.get(node_id)
|
|
@@ -403,14 +477,30 @@ class MergedPlotUpdater:
|
|
|
403
477
|
line = self.timehistory_lines.get(label)
|
|
404
478
|
if line is not None:
|
|
405
479
|
line.set_xdata(xdata)
|
|
406
|
-
|
|
480
|
+
series = ydata[:, idx]
|
|
481
|
+
line.set_ydata(series)
|
|
482
|
+
if self.view.time_y_auto and series is not None and len(series) > 0:
|
|
483
|
+
finite = series[np.isfinite(series)]
|
|
484
|
+
if finite.size:
|
|
485
|
+
smin = float(finite.min())
|
|
486
|
+
smax = float(finite.max())
|
|
487
|
+
min_time_y = smin if min_time_y is None else min(min_time_y, smin)
|
|
488
|
+
max_time_y = smax if max_time_y is None else max(max_time_y, smax)
|
|
407
489
|
if self.model.merged_timehistory_xlim:
|
|
408
490
|
self.axes1.set_xlim(self.model.merged_timehistory_xlim)
|
|
491
|
+
if self.view.time_y_auto and min_time_y is not None and max_time_y is not None:
|
|
492
|
+
if min_time_y == max_time_y:
|
|
493
|
+
pad = 1.0 if min_time_y == 0 else abs(min_time_y) * 0.1
|
|
494
|
+
else:
|
|
495
|
+
pad = (max_time_y - min_time_y) * 0.05
|
|
496
|
+
self.axes1.set_ylim(min_time_y - pad, max_time_y + pad)
|
|
409
497
|
try:
|
|
410
498
|
self.view.m_fig_merged.figure.canvas.draw()
|
|
411
499
|
except Exception:
|
|
412
500
|
raise
|
|
413
501
|
|
|
502
|
+
min_psd_y = None
|
|
503
|
+
max_psd_y = None
|
|
414
504
|
f = self.model.merged_psd_xdata
|
|
415
505
|
if f is None:
|
|
416
506
|
f = []
|
|
@@ -424,15 +514,37 @@ class MergedPlotUpdater:
|
|
|
424
514
|
line = self.psd_lines.get(label)
|
|
425
515
|
if line is not None:
|
|
426
516
|
line.set_xdata(f)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
517
|
+
series = ydata[:, idx]
|
|
518
|
+
line.set_ydata(series)
|
|
519
|
+
if self.view.psd_y_auto and series is not None and len(series) > 0:
|
|
520
|
+
finite = series[np.isfinite(series)]
|
|
521
|
+
finite = finite[finite > 0]
|
|
522
|
+
if finite.size:
|
|
523
|
+
smin = float(finite.min())
|
|
524
|
+
smax = float(finite.max())
|
|
525
|
+
min_psd_y = smin if min_psd_y is None else min(min_psd_y, smin)
|
|
526
|
+
max_psd_y = smax if max_psd_y is None else max(max_psd_y, smax)
|
|
527
|
+
|
|
528
|
+
if self.psd_auto_x and len(f) > 0:
|
|
430
529
|
self.axes2.set_xlim(0, round(f[-1]))
|
|
530
|
+
if self.view.psd_y_auto and min_psd_y is not None and max_psd_y is not None:
|
|
531
|
+
low = min_psd_y * 0.8
|
|
532
|
+
high = max_psd_y * 1.2
|
|
533
|
+
if low <= 0:
|
|
534
|
+
low = min_psd_y * 0.5
|
|
535
|
+
if low > 0 and high > low:
|
|
536
|
+
self.axes2.set_ylim(low, high)
|
|
431
537
|
try:
|
|
432
538
|
self.view.m_fig_merged.figure.canvas.draw()
|
|
433
539
|
except Exception:
|
|
434
540
|
raise
|
|
435
541
|
|
|
542
|
+
def _on_psd_xlim_changed(self, _axes):
|
|
543
|
+
# Disable auto-scaling after user zoom/pan.
|
|
544
|
+
if not self.psd_auto_x:
|
|
545
|
+
return
|
|
546
|
+
self.psd_auto_x = False
|
|
547
|
+
|
|
436
548
|
###########################################################################
|
|
437
549
|
## Class View
|
|
438
550
|
###########################################################################
|
|
@@ -447,12 +559,19 @@ class View(wx.Frame):
|
|
|
447
559
|
self.model = self.controller.model
|
|
448
560
|
if self.model is not None:
|
|
449
561
|
self.timespan_length = self.model.timespan_length
|
|
562
|
+
else:
|
|
563
|
+
self.timespan_length = 30
|
|
450
564
|
self.m_table = None
|
|
451
565
|
self.table_updater = None
|
|
452
566
|
self.plot_updater = None
|
|
453
567
|
self.merged_plot_updater = None
|
|
454
568
|
self.PPS_outdate_check = True
|
|
455
569
|
self.channel_selection = [False, True, False]
|
|
570
|
+
self.time_y_auto = False
|
|
571
|
+
self.time_y_limits = (-2.0, 2.0)
|
|
572
|
+
self.psd_y_auto = False
|
|
573
|
+
self.psd_y_limits = (1e-6, 1e1)
|
|
574
|
+
self._load_plot_options()
|
|
456
575
|
|
|
457
576
|
self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
|
|
458
577
|
|
|
@@ -479,17 +598,14 @@ class View(wx.Frame):
|
|
|
479
598
|
self.m_panel_status = wx.Panel(self.m_left_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL)
|
|
480
599
|
self.m_panel_map = wx.Panel(self.m_left_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL)
|
|
481
600
|
self.m_panel_plots = wx.Panel(self.m_left_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL)
|
|
482
|
-
self.m_panel_merged_plots = wx.Panel(self.m_left_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL)
|
|
483
601
|
self.m_left_notebook.AddPage(self.m_panel_nodes, _(u"Nodes"), False)
|
|
484
602
|
self.m_left_notebook.AddPage(self.m_panel_status, _(u"Mesh"), True)
|
|
485
603
|
self.m_left_notebook.AddPage(self.m_panel_map, _(u"Map"), False)
|
|
486
|
-
self.m_left_notebook.AddPage(self.m_panel_plots, _(u"
|
|
487
|
-
self.m_left_notebook.AddPage(self.m_panel_merged_plots, _(u"Merged Plot"), False)
|
|
604
|
+
self.m_left_notebook.AddPage(self.m_panel_plots, _(u"Plots"), False)
|
|
488
605
|
self.tab_index_nodes = 0
|
|
489
606
|
self.tab_index_status = 1
|
|
490
607
|
self.tab_index_map = 2
|
|
491
608
|
self.tab_index_rt_plots = 3
|
|
492
|
-
self.tab_index_merged_plots = 4
|
|
493
609
|
|
|
494
610
|
# Sensor Nodes definition tab
|
|
495
611
|
nodes_sizer = wx.BoxSizer(wx.VERTICAL)
|
|
@@ -505,7 +621,7 @@ class View(wx.Frame):
|
|
|
505
621
|
self.controller.model.options.get('acc_nums_txt', '[1:3]'),
|
|
506
622
|
wx.DefaultPosition,
|
|
507
623
|
wx.DefaultSize,
|
|
508
|
-
|
|
624
|
+
wx.TE_PROCESS_ENTER,
|
|
509
625
|
)
|
|
510
626
|
nodes_grid.Add(self.m_textCtrl_acc, 1, wx.EXPAND)
|
|
511
627
|
|
|
@@ -517,7 +633,7 @@ class View(wx.Frame):
|
|
|
517
633
|
self.controller.model.options.get('tmp_nums_txt', '[]'),
|
|
518
634
|
wx.DefaultPosition,
|
|
519
635
|
wx.DefaultSize,
|
|
520
|
-
|
|
636
|
+
wx.TE_PROCESS_ENTER,
|
|
521
637
|
)
|
|
522
638
|
nodes_grid.Add(self.m_textCtrl_tmp, 1, wx.EXPAND)
|
|
523
639
|
|
|
@@ -529,7 +645,7 @@ class View(wx.Frame):
|
|
|
529
645
|
self.controller.model.options.get('str_nums_txt', '[]'),
|
|
530
646
|
wx.DefaultPosition,
|
|
531
647
|
wx.DefaultSize,
|
|
532
|
-
|
|
648
|
+
wx.TE_PROCESS_ENTER,
|
|
533
649
|
)
|
|
534
650
|
nodes_grid.Add(self.m_textCtrl_str, 1, wx.EXPAND)
|
|
535
651
|
|
|
@@ -541,7 +657,7 @@ class View(wx.Frame):
|
|
|
541
657
|
self.controller.model.options.get('veh_nums_txt', '[]'),
|
|
542
658
|
wx.DefaultPosition,
|
|
543
659
|
wx.DefaultSize,
|
|
544
|
-
|
|
660
|
+
wx.TE_PROCESS_ENTER,
|
|
545
661
|
)
|
|
546
662
|
nodes_grid.Add(self.m_textCtrl_veh, 1, wx.EXPAND)
|
|
547
663
|
|
|
@@ -573,6 +689,7 @@ class View(wx.Frame):
|
|
|
573
689
|
self.m_map = webview.WebView.New(self.m_panel_map)
|
|
574
690
|
map_sizer.Add(self.m_map, 1, wx.ALL | wx.EXPAND, 5)
|
|
575
691
|
self.m_map_loaded = False
|
|
692
|
+
self._map_nodes = set()
|
|
576
693
|
self.m_map.Bind(webview.EVT_WEBVIEW_LOADED, self.on_map_loaded)
|
|
577
694
|
self._load_map()
|
|
578
695
|
else:
|
|
@@ -586,14 +703,88 @@ class View(wx.Frame):
|
|
|
586
703
|
self.m_panel_map.SetSizer(map_sizer)
|
|
587
704
|
|
|
588
705
|
plots_sizer = wx.BoxSizer(wx.VERTICAL)
|
|
706
|
+
|
|
707
|
+
plot_controls_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
708
|
+
|
|
709
|
+
self.m_button_clf = wx.Button(self.m_panel_plots, wx.ID_ANY, _(u"Clear Figure"), wx.DefaultPosition, wx.DefaultSize, 0)
|
|
710
|
+
plot_controls_sizer.Add(self.m_button_clf, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
711
|
+
|
|
712
|
+
plot_controls_sizer.Add(wx.StaticText(self.m_panel_plots, wx.ID_ANY, _(u"Channels:")), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
713
|
+
self.m_check_chx = wx.CheckBox(self.m_panel_plots, wx.ID_ANY, _(u"X"))
|
|
714
|
+
self.m_check_chy = wx.CheckBox(self.m_panel_plots, wx.ID_ANY, _(u"Y"))
|
|
715
|
+
self.m_check_chz = wx.CheckBox(self.m_panel_plots, wx.ID_ANY, _(u"Z"))
|
|
716
|
+
self.m_check_chx.SetValue(self.channel_selection[0])
|
|
717
|
+
self.m_check_chy.SetValue(self.channel_selection[1])
|
|
718
|
+
self.m_check_chz.SetValue(self.channel_selection[2])
|
|
719
|
+
plot_controls_sizer.Add(self.m_check_chx, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
720
|
+
plot_controls_sizer.Add(self.m_check_chy, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
721
|
+
plot_controls_sizer.Add(self.m_check_chz, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
722
|
+
|
|
723
|
+
plot_controls_sizer.Add(wx.StaticText(self.m_panel_plots, wx.ID_ANY, _(u"Timespan:")), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
724
|
+
self.timespan_choices = [30, 20, 10, 5, 2]
|
|
725
|
+
self.m_choice_timespan = wx.Choice(
|
|
726
|
+
self.m_panel_plots,
|
|
727
|
+
choices=[f"{v} sec" for v in self.timespan_choices],
|
|
728
|
+
)
|
|
729
|
+
if self.timespan_length in self.timespan_choices:
|
|
730
|
+
self.m_choice_timespan.SetSelection(self.timespan_choices.index(self.timespan_length))
|
|
731
|
+
else:
|
|
732
|
+
self.m_choice_timespan.SetSelection(0)
|
|
733
|
+
plot_controls_sizer.Add(self.m_choice_timespan, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)
|
|
734
|
+
|
|
735
|
+
plots_sizer.Add(plot_controls_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5)
|
|
736
|
+
|
|
737
|
+
ylim_sizer = wx.FlexGridSizer(1, 6, 2, 6)
|
|
738
|
+
ylim_sizer.AddGrowableCol(2, 1)
|
|
739
|
+
ylim_sizer.AddGrowableCol(5, 1)
|
|
740
|
+
|
|
741
|
+
self.m_check_time_y_auto = wx.CheckBox(self.m_panel_plots, wx.ID_ANY, _(u"Time Y auto"))
|
|
742
|
+
self.m_check_time_y_auto.SetValue(self.time_y_auto)
|
|
743
|
+
ylim_sizer.Add(self.m_check_time_y_auto, 0, wx.ALIGN_CENTER_VERTICAL)
|
|
744
|
+
ylim_sizer.Add(wx.StaticText(self.m_panel_plots, wx.ID_ANY, _(u"ylim:")), 0, wx.ALIGN_CENTER_VERTICAL)
|
|
745
|
+
self.m_text_time_ylim = wx.TextCtrl(
|
|
746
|
+
self.m_panel_plots,
|
|
747
|
+
wx.ID_ANY,
|
|
748
|
+
f"[{self.time_y_limits[0]}, {self.time_y_limits[1]}]",
|
|
749
|
+
wx.DefaultPosition,
|
|
750
|
+
wx.Size(120, -1),
|
|
751
|
+
wx.TE_PROCESS_ENTER,
|
|
752
|
+
)
|
|
753
|
+
self.m_text_time_ylim.Enable(not self.time_y_auto)
|
|
754
|
+
ylim_sizer.Add(self.m_text_time_ylim, 0, wx.ALIGN_CENTER_VERTICAL)
|
|
755
|
+
|
|
756
|
+
self.m_check_psd_y_auto = wx.CheckBox(self.m_panel_plots, wx.ID_ANY, _(u"ASD Y auto"))
|
|
757
|
+
self.m_check_psd_y_auto.SetValue(self.psd_y_auto)
|
|
758
|
+
ylim_sizer.Add(self.m_check_psd_y_auto, 0, wx.ALIGN_CENTER_VERTICAL)
|
|
759
|
+
ylim_sizer.Add(wx.StaticText(self.m_panel_plots, wx.ID_ANY, _(u"ylim:")), 0, wx.ALIGN_CENTER_VERTICAL)
|
|
760
|
+
self.m_text_psd_ylim = wx.TextCtrl(
|
|
761
|
+
self.m_panel_plots,
|
|
762
|
+
wx.ID_ANY,
|
|
763
|
+
f"[{self.psd_y_limits[0]}, {self.psd_y_limits[1]}]",
|
|
764
|
+
wx.DefaultPosition,
|
|
765
|
+
wx.Size(120, -1),
|
|
766
|
+
wx.TE_PROCESS_ENTER,
|
|
767
|
+
)
|
|
768
|
+
self.m_text_psd_ylim.Enable(not self.psd_y_auto)
|
|
769
|
+
ylim_sizer.Add(self.m_text_psd_ylim, 0, wx.ALIGN_CENTER_VERTICAL)
|
|
770
|
+
|
|
771
|
+
ylim_row = wx.BoxSizer(wx.HORIZONTAL)
|
|
772
|
+
indent = self.m_button_clf.GetBestSize().width + 8
|
|
773
|
+
ylim_row.AddSpacer(indent)
|
|
774
|
+
ylim_row.Add(ylim_sizer, 0, wx.ALIGN_CENTER_VERTICAL)
|
|
775
|
+
plots_sizer.Add(ylim_row, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 5)
|
|
589
776
|
self.m_plot_panel = wx.Panel(self.m_panel_plots, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL)
|
|
590
777
|
plots_sizer.Add(self.m_plot_panel, 1, wx.ALL | wx.EXPAND, 5)
|
|
591
778
|
self.m_panel_plots.SetSizer(plots_sizer)
|
|
592
779
|
|
|
593
|
-
|
|
594
|
-
self.
|
|
595
|
-
|
|
596
|
-
self.
|
|
780
|
+
self.m_check_chx.Bind(wx.EVT_CHECKBOX, self.onChannelToggled)
|
|
781
|
+
self.m_check_chy.Bind(wx.EVT_CHECKBOX, self.onChannelToggled)
|
|
782
|
+
self.m_check_chz.Bind(wx.EVT_CHECKBOX, self.onChannelToggled)
|
|
783
|
+
self.m_choice_timespan.Bind(wx.EVT_CHOICE, self.onTimeSpanChange)
|
|
784
|
+
self.m_check_time_y_auto.Bind(wx.EVT_CHECKBOX, self.on_time_y_auto_toggle)
|
|
785
|
+
self.m_text_time_ylim.Bind(wx.EVT_TEXT_ENTER, self.on_time_ylim_enter)
|
|
786
|
+
self.m_check_psd_y_auto.Bind(wx.EVT_CHECKBOX, self.on_psd_y_auto_toggle)
|
|
787
|
+
self.m_text_psd_ylim.Bind(wx.EVT_TEXT_ENTER, self.on_psd_ylim_enter)
|
|
597
788
|
|
|
598
789
|
bSizer3.Add(self.m_left_notebook, 1, wx.ALL | wx.EXPAND, 5)
|
|
599
790
|
bSizer1.Add( bSizer3, 1, wx.EXPAND, 5 )
|
|
@@ -613,6 +804,7 @@ class View(wx.Frame):
|
|
|
613
804
|
"SD Stream Start",
|
|
614
805
|
"SD Stream Stop",
|
|
615
806
|
"SD Clear All",
|
|
807
|
+
"Shutdown",
|
|
616
808
|
],
|
|
617
809
|
)
|
|
618
810
|
cmd_choice_sizer.Add(self.m_choice_OperationMode, 0, wx.ALL | wx.CENTER, 5)
|
|
@@ -681,46 +873,6 @@ class View(wx.Frame):
|
|
|
681
873
|
self.m_menu_data.AppendSubMenu(self.m_menu_data_clear, _(u"Clear"))
|
|
682
874
|
self.m_menubar.Append(self.m_menu_data, _(u"&Data"))
|
|
683
875
|
|
|
684
|
-
self.m_menu_cmd = wx.Menu()
|
|
685
|
-
self.m_menu_cmd_sdclear = wx.MenuItem(self.m_menu_cmd, wx.ID_ANY, _(u"SD &Clear"), wx.EmptyString, wx.ITEM_NORMAL)
|
|
686
|
-
self.m_menu_cmd.Append(self.m_menu_cmd_sdclear)
|
|
687
|
-
self.m_menu_cmd_shutdown = wx.MenuItem(self.m_menu_cmd, wx.ID_ANY, _(u"&Shutdown"), wx.EmptyString, wx.ITEM_NORMAL)
|
|
688
|
-
self.m_menu_cmd.Append(self.m_menu_cmd_shutdown)
|
|
689
|
-
self.m_menubar.Append(self.m_menu_cmd, _(u"&CMD"))
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
self.m_menu_view = wx.Menu()
|
|
693
|
-
self.m_menu_view_clf = wx.MenuItem(self.m_menu_view, wx.ID_ANY, _(u"&Clear Figure"), wx.EmptyString, wx.ITEM_NORMAL)
|
|
694
|
-
self.m_menu_view.Append(self.m_menu_view_clf)
|
|
695
|
-
|
|
696
|
-
self.m_menu_view_chsel = wx.Menu()
|
|
697
|
-
self.m_menu_view_chsel_x = self.m_menu_view_chsel.AppendCheckItem(wx.ID_ANY, "X")
|
|
698
|
-
self.m_menu_view_chsel_y = self.m_menu_view_chsel.AppendCheckItem(wx.ID_ANY, "Y")
|
|
699
|
-
self.m_menu_view_chsel_z = self.m_menu_view_chsel.AppendCheckItem(wx.ID_ANY, "Z")
|
|
700
|
-
self.m_menu_view_chsel_y.Check(True)
|
|
701
|
-
self.m_menu_view.AppendSubMenu(self.m_menu_view_chsel, "Channel Selection")
|
|
702
|
-
self.Bind(wx.EVT_MENU, self.onChannelToggled, self.m_menu_view_chsel_x)
|
|
703
|
-
self.Bind(wx.EVT_MENU, self.onChannelToggled, self.m_menu_view_chsel_y)
|
|
704
|
-
self.Bind(wx.EVT_MENU, self.onChannelToggled, self.m_menu_view_chsel_z)
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
self.m_menu_view_timespan = wx.Menu()
|
|
708
|
-
self.m_menu_view_timespan_radio_30s = self.m_menu_view_timespan.AppendRadioItem(wx.ID_ANY, "30 sec")
|
|
709
|
-
self.m_menu_view_timespan_radio_20s = self.m_menu_view_timespan.AppendRadioItem(wx.ID_ANY, "20 sec")
|
|
710
|
-
self.m_menu_view_timespan_radio_10s = self.m_menu_view_timespan.AppendRadioItem(wx.ID_ANY, "10 sec")
|
|
711
|
-
self.m_menu_view_timespan_radio_05s = self.m_menu_view_timespan.AppendRadioItem(wx.ID_ANY, "5 sec")
|
|
712
|
-
self.m_menu_view_timespan_radio_02s = self.m_menu_view_timespan.AppendRadioItem(wx.ID_ANY, "2 sec")
|
|
713
|
-
self.m_menu_view_timespan_radio_30s.Check(True)
|
|
714
|
-
self.m_menu_view.AppendSubMenu(self.m_menu_view_timespan, "Timespan Select")
|
|
715
|
-
self.Bind(wx.EVT_MENU, self.onTimeSpanChange, self.m_menu_view_timespan_radio_30s)
|
|
716
|
-
self.Bind(wx.EVT_MENU, self.onTimeSpanChange, self.m_menu_view_timespan_radio_20s)
|
|
717
|
-
self.Bind(wx.EVT_MENU, self.onTimeSpanChange, self.m_menu_view_timespan_radio_10s)
|
|
718
|
-
self.Bind(wx.EVT_MENU, self.onTimeSpanChange, self.m_menu_view_timespan_radio_05s)
|
|
719
|
-
self.Bind(wx.EVT_MENU, self.onTimeSpanChange, self.m_menu_view_timespan_radio_02s)
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
self.m_menubar.Append(self.m_menu_view, _(u"&View"))
|
|
723
|
-
|
|
724
876
|
self.m_menu_help = wx.Menu()
|
|
725
877
|
self.m_menu_help_about = wx.MenuItem(self.m_menu_help, wx.ID_ANY, _(u"&Help"), wx.EmptyString, wx.ITEM_NORMAL)
|
|
726
878
|
self.m_menu_help.Append(self.m_menu_help_about)
|
|
@@ -739,23 +891,25 @@ class View(wx.Frame):
|
|
|
739
891
|
self.plot_updater = PlotUpdater(self)
|
|
740
892
|
self.plot_updater.init_plot()
|
|
741
893
|
self.m_plot_canvas = self.m_fig.canvas
|
|
742
|
-
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
743
|
-
sizer.Add(self.m_fig, 1, wx.EXPAND)
|
|
744
|
-
self.m_plot_panel.SetSizer(sizer)
|
|
745
894
|
self.m_plot_canvas.Bind(wx.EVT_LEFT_DOWN, self._focus_plot_canvas)
|
|
746
895
|
|
|
747
|
-
self.m_fig_merged = Plot(self.
|
|
896
|
+
self.m_fig_merged = Plot(self.m_plot_panel)
|
|
748
897
|
self.axes1_merged = self.m_fig_merged.figure.add_subplot(2, 1, 1)
|
|
749
898
|
self.axes2_merged = self.m_fig_merged.figure.add_subplot(2, 1, 2)
|
|
750
899
|
self.m_fig_merged.figure.subplots_adjust(hspace=0.28, bottom=0.08, top=0.95)
|
|
751
900
|
self.merged_plot_updater = MergedPlotUpdater(self)
|
|
752
901
|
self.merged_plot_updater.init_plot()
|
|
753
902
|
self.m_plot_canvas_merged = self.m_fig_merged.canvas
|
|
754
|
-
merged_sizer = wx.BoxSizer(wx.VERTICAL)
|
|
755
|
-
merged_sizer.Add(self.m_fig_merged, 1, wx.EXPAND)
|
|
756
|
-
self.m_plot_panel_merged.SetSizer(merged_sizer)
|
|
757
903
|
self.m_plot_canvas_merged.Bind(wx.EVT_LEFT_DOWN, self._focus_plot_canvas)
|
|
758
904
|
|
|
905
|
+
plot_stack = wx.BoxSizer(wx.VERTICAL)
|
|
906
|
+
plot_stack.Add(self.m_fig, 1, wx.EXPAND)
|
|
907
|
+
plot_stack.Add(self.m_fig_merged, 1, wx.EXPAND)
|
|
908
|
+
self.m_fig_merged.Hide()
|
|
909
|
+
self.m_plot_panel.SetSizer(plot_stack)
|
|
910
|
+
self._plot_stack = plot_stack
|
|
911
|
+
self._active_plot = "rt"
|
|
912
|
+
|
|
759
913
|
# UPDATE TIMERS
|
|
760
914
|
|
|
761
915
|
self._last_plot_version = 0
|
|
@@ -796,14 +950,6 @@ class View(wx.Frame):
|
|
|
796
950
|
self.m_left_notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_tab_changed)
|
|
797
951
|
|
|
798
952
|
|
|
799
|
-
self.timespan_map = {
|
|
800
|
-
self.m_menu_view_timespan_radio_30s.GetId(): 30,
|
|
801
|
-
self.m_menu_view_timespan_radio_20s.GetId(): 20,
|
|
802
|
-
self.m_menu_view_timespan_radio_10s.GetId(): 10,
|
|
803
|
-
self.m_menu_view_timespan_radio_05s.GetId(): 5,
|
|
804
|
-
self.m_menu_view_timespan_radio_02s.GetId(): 2,
|
|
805
|
-
}
|
|
806
|
-
|
|
807
953
|
def mesh_status_data_view(self):
|
|
808
954
|
if self.table_updater is None:
|
|
809
955
|
self.table_updater = TableUpdater(self)
|
|
@@ -821,6 +967,15 @@ class View(wx.Frame):
|
|
|
821
967
|
self.m_textCtrl_tmp,
|
|
822
968
|
self.m_textCtrl_str,
|
|
823
969
|
self.m_textCtrl_veh,
|
|
970
|
+
getattr(self, "m_button_clf", None),
|
|
971
|
+
getattr(self, "m_check_chx", None),
|
|
972
|
+
getattr(self, "m_check_chy", None),
|
|
973
|
+
getattr(self, "m_check_chz", None),
|
|
974
|
+
getattr(self, "m_choice_timespan", None),
|
|
975
|
+
getattr(self, "m_check_time_y_auto", None),
|
|
976
|
+
getattr(self, "m_text_time_ylim", None),
|
|
977
|
+
getattr(self, "m_check_psd_y_auto", None),
|
|
978
|
+
getattr(self, "m_text_psd_ylim", None),
|
|
824
979
|
getattr(self, "m_plot_canvas", None),
|
|
825
980
|
getattr(self, "m_plot_canvas_merged", None),
|
|
826
981
|
):
|
|
@@ -900,9 +1055,32 @@ class View(wx.Frame):
|
|
|
900
1055
|
return
|
|
901
1056
|
self.merged_plot_updater.figure_update()
|
|
902
1057
|
|
|
1058
|
+
def _set_active_plot(self, mode: str):
|
|
1059
|
+
if mode not in ("rt", "merged"):
|
|
1060
|
+
return
|
|
1061
|
+
if getattr(self, "_active_plot", None) == mode:
|
|
1062
|
+
return
|
|
1063
|
+
self._active_plot = mode
|
|
1064
|
+
if mode == "merged":
|
|
1065
|
+
self.m_fig.Hide()
|
|
1066
|
+
self.m_fig_merged.Show()
|
|
1067
|
+
else:
|
|
1068
|
+
self.m_fig_merged.Hide()
|
|
1069
|
+
self.m_fig.Show()
|
|
1070
|
+
try:
|
|
1071
|
+
self.m_plot_panel.Layout()
|
|
1072
|
+
except Exception:
|
|
1073
|
+
pass
|
|
1074
|
+
|
|
1075
|
+
def show_rt_plots(self):
|
|
1076
|
+
self._set_active_plot("rt")
|
|
1077
|
+
if hasattr(self, "tab_index_rt_plots"):
|
|
1078
|
+
self.m_left_notebook.SetSelection(self.tab_index_rt_plots)
|
|
1079
|
+
|
|
903
1080
|
def show_merged_plots(self):
|
|
904
|
-
|
|
905
|
-
|
|
1081
|
+
self._set_active_plot("merged")
|
|
1082
|
+
if hasattr(self, "tab_index_rt_plots"):
|
|
1083
|
+
self.m_left_notebook.SetSelection(self.tab_index_rt_plots)
|
|
906
1084
|
|
|
907
1085
|
def _focus_plot_canvas(self, event):
|
|
908
1086
|
try:
|
|
@@ -986,14 +1164,12 @@ class View(wx.Frame):
|
|
|
986
1164
|
|
|
987
1165
|
def on_tab_changed(self, event):
|
|
988
1166
|
idx = event.GetSelection()
|
|
989
|
-
if idx == self.tab_index_rt_plots
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
pass
|
|
994
|
-
if idx == self.tab_index_merged_plots and hasattr(self, "m_plot_canvas_merged"):
|
|
1167
|
+
if idx == self.tab_index_rt_plots:
|
|
1168
|
+
canvas = self.m_plot_canvas
|
|
1169
|
+
if getattr(self, "_active_plot", "rt") == "merged":
|
|
1170
|
+
canvas = self.m_plot_canvas_merged
|
|
995
1171
|
try:
|
|
996
|
-
|
|
1172
|
+
canvas.SetFocus()
|
|
997
1173
|
except Exception:
|
|
998
1174
|
pass
|
|
999
1175
|
event.Skip()
|
|
@@ -1004,10 +1180,24 @@ class View(wx.Frame):
|
|
|
1004
1180
|
def map_update(self, event):
|
|
1005
1181
|
if self.m_map is None or not self.m_map_loaded:
|
|
1006
1182
|
return
|
|
1183
|
+
connected = set()
|
|
1184
|
+
if self.model is not None:
|
|
1185
|
+
for _, row in self.model.mesh_status_data.iterrows():
|
|
1186
|
+
if row.get("Connection") == "connected":
|
|
1187
|
+
connected.add(row.get("nodeID"))
|
|
1188
|
+
|
|
1189
|
+
for node_id in list(self._map_nodes):
|
|
1190
|
+
if node_id not in connected:
|
|
1191
|
+
safe_id = str(node_id).replace("'", "\\'")
|
|
1192
|
+
self.m_map.RunScript(f"removeMarker('{safe_id}');")
|
|
1193
|
+
self._map_nodes.discard(node_id)
|
|
1194
|
+
|
|
1007
1195
|
positions = getattr(self.model, "gnss_positions", {})
|
|
1008
1196
|
if not positions:
|
|
1009
1197
|
return
|
|
1010
1198
|
for node_id, pos in positions.items():
|
|
1199
|
+
if node_id not in connected:
|
|
1200
|
+
continue
|
|
1011
1201
|
if not pos.get("valid"):
|
|
1012
1202
|
continue
|
|
1013
1203
|
lat = pos.get("lat")
|
|
@@ -1016,6 +1206,7 @@ class View(wx.Frame):
|
|
|
1016
1206
|
continue
|
|
1017
1207
|
safe_id = str(node_id).replace("'", "\\'")
|
|
1018
1208
|
self.m_map.RunScript(f"updateMarker('{safe_id}', {lat}, {lon});")
|
|
1209
|
+
self._map_nodes.add(node_id)
|
|
1019
1210
|
|
|
1020
1211
|
def _load_map(self):
|
|
1021
1212
|
if self.m_map is None:
|
|
@@ -1043,6 +1234,26 @@ class View(wx.Frame):
|
|
|
1043
1234
|
attribution: '© OpenStreetMap contributors'
|
|
1044
1235
|
}).addTo(map);
|
|
1045
1236
|
const markers = {};
|
|
1237
|
+
let bounds = L.latLngBounds();
|
|
1238
|
+
let autoFit = true;
|
|
1239
|
+
|
|
1240
|
+
function rebuildBounds() {
|
|
1241
|
+
bounds = L.latLngBounds();
|
|
1242
|
+
let hasMarker = false;
|
|
1243
|
+
for (const key in markers) {
|
|
1244
|
+
if (!Object.prototype.hasOwnProperty.call(markers, key)) continue;
|
|
1245
|
+
const latlng = markers[key].getLatLng();
|
|
1246
|
+
bounds.extend(latlng);
|
|
1247
|
+
hasMarker = true;
|
|
1248
|
+
}
|
|
1249
|
+
if (autoFit && hasMarker && bounds.isValid()) {
|
|
1250
|
+
map.fitBounds(bounds.pad(0.15), {animate: false});
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
map.on('dragstart zoomstart', () => {
|
|
1255
|
+
autoFit = false;
|
|
1256
|
+
});
|
|
1046
1257
|
window.updateMarker = function(nodeId, lat, lon) {
|
|
1047
1258
|
let m = markers[nodeId];
|
|
1048
1259
|
if (!m) {
|
|
@@ -1051,6 +1262,17 @@ class View(wx.Frame):
|
|
|
1051
1262
|
markers[nodeId] = m;
|
|
1052
1263
|
}
|
|
1053
1264
|
m.setLatLng([lat, lon]);
|
|
1265
|
+
bounds.extend([lat, lon]);
|
|
1266
|
+
if (autoFit && bounds.isValid()) {
|
|
1267
|
+
map.fitBounds(bounds.pad(0.15), {animate: false});
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
window.removeMarker = function(nodeId) {
|
|
1271
|
+
const m = markers[nodeId];
|
|
1272
|
+
if (!m) return;
|
|
1273
|
+
map.removeLayer(m);
|
|
1274
|
+
delete markers[nodeId];
|
|
1275
|
+
rebuildBounds();
|
|
1054
1276
|
};
|
|
1055
1277
|
</script>
|
|
1056
1278
|
</body>
|
|
@@ -1136,14 +1358,156 @@ class View(wx.Frame):
|
|
|
1136
1358
|
return
|
|
1137
1359
|
self.plot_updater.update_psd_legend()
|
|
1138
1360
|
|
|
1361
|
+
def _parse_ylim_text(self, text: str):
|
|
1362
|
+
if text is None:
|
|
1363
|
+
return None
|
|
1364
|
+
raw = text.strip()
|
|
1365
|
+
if not raw:
|
|
1366
|
+
return None
|
|
1367
|
+
if raw.startswith("[") and raw.endswith("]"):
|
|
1368
|
+
raw = raw[1:-1].strip()
|
|
1369
|
+
parts = [p.strip() for p in raw.split(",") if p.strip()]
|
|
1370
|
+
if len(parts) != 2:
|
|
1371
|
+
parts = [p.strip() for p in raw.split() if p.strip()]
|
|
1372
|
+
if len(parts) != 2:
|
|
1373
|
+
return None
|
|
1374
|
+
try:
|
|
1375
|
+
lo = float(parts[0])
|
|
1376
|
+
hi = float(parts[1])
|
|
1377
|
+
except (TypeError, ValueError):
|
|
1378
|
+
return None
|
|
1379
|
+
if lo == hi:
|
|
1380
|
+
return None
|
|
1381
|
+
if lo > hi:
|
|
1382
|
+
lo, hi = hi, lo
|
|
1383
|
+
return lo, hi
|
|
1384
|
+
|
|
1385
|
+
def _parse_bool_option(self, value, default: bool) -> bool:
|
|
1386
|
+
if value is None:
|
|
1387
|
+
return default
|
|
1388
|
+
if isinstance(value, bool):
|
|
1389
|
+
return value
|
|
1390
|
+
text = str(value).strip().lower()
|
|
1391
|
+
if text in {"1", "true", "yes", "y", "on"}:
|
|
1392
|
+
return True
|
|
1393
|
+
if text in {"0", "false", "no", "n", "off"}:
|
|
1394
|
+
return False
|
|
1395
|
+
return default
|
|
1396
|
+
|
|
1397
|
+
def _parse_int_option(self, value, default: int) -> int:
|
|
1398
|
+
if value is None:
|
|
1399
|
+
return default
|
|
1400
|
+
try:
|
|
1401
|
+
return int(str(value).strip())
|
|
1402
|
+
except (TypeError, ValueError):
|
|
1403
|
+
return default
|
|
1139
1404
|
|
|
1140
|
-
def
|
|
1141
|
-
|
|
1405
|
+
def _load_plot_options(self) -> None:
|
|
1406
|
+
if self.model is None:
|
|
1407
|
+
return
|
|
1408
|
+
options = getattr(self.model, "options", {}) or {}
|
|
1142
1409
|
self.channel_selection = [
|
|
1143
|
-
self.
|
|
1144
|
-
self.
|
|
1145
|
-
self.
|
|
1410
|
+
self._parse_bool_option(options.get("plot_channel_x"), self.channel_selection[0]),
|
|
1411
|
+
self._parse_bool_option(options.get("plot_channel_y"), self.channel_selection[1]),
|
|
1412
|
+
self._parse_bool_option(options.get("plot_channel_z"), self.channel_selection[2]),
|
|
1146
1413
|
]
|
|
1414
|
+
self.timespan_length = self._parse_int_option(
|
|
1415
|
+
options.get("plot_timespan"), self.timespan_length
|
|
1416
|
+
)
|
|
1417
|
+
self.model.timespan_length = self.timespan_length
|
|
1418
|
+
self.time_y_auto = self._parse_bool_option(
|
|
1419
|
+
options.get("plot_time_y_auto"), self.time_y_auto
|
|
1420
|
+
)
|
|
1421
|
+
time_ylim = self._parse_ylim_text(options.get("plot_time_ylim", ""))
|
|
1422
|
+
if time_ylim is not None:
|
|
1423
|
+
self.time_y_limits = time_ylim
|
|
1424
|
+
self.psd_y_auto = self._parse_bool_option(
|
|
1425
|
+
options.get("plot_psd_y_auto"), self.psd_y_auto
|
|
1426
|
+
)
|
|
1427
|
+
psd_ylim = self._parse_ylim_text(options.get("plot_psd_ylim", ""))
|
|
1428
|
+
if psd_ylim is not None:
|
|
1429
|
+
self.psd_y_limits = psd_ylim
|
|
1430
|
+
|
|
1431
|
+
def _persist_plot_options(self) -> None:
|
|
1432
|
+
if self.model is None:
|
|
1433
|
+
return
|
|
1434
|
+
options = getattr(self.model, "options", None)
|
|
1435
|
+
if options is None:
|
|
1436
|
+
return
|
|
1437
|
+
options["plot_channel_x"] = "true" if self.channel_selection[0] else "false"
|
|
1438
|
+
options["plot_channel_y"] = "true" if self.channel_selection[1] else "false"
|
|
1439
|
+
options["plot_channel_z"] = "true" if self.channel_selection[2] else "false"
|
|
1440
|
+
options["plot_timespan"] = str(self.timespan_length)
|
|
1441
|
+
options["plot_time_y_auto"] = "true" if self.time_y_auto else "false"
|
|
1442
|
+
options["plot_time_ylim"] = f"[{self.time_y_limits[0]}, {self.time_y_limits[1]}]"
|
|
1443
|
+
options["plot_psd_y_auto"] = "true" if self.psd_y_auto else "false"
|
|
1444
|
+
options["plot_psd_ylim"] = f"[{self.psd_y_limits[0]}, {self.psd_y_limits[1]}]"
|
|
1445
|
+
self.model.save_config()
|
|
1446
|
+
|
|
1447
|
+
def on_time_y_auto_toggle(self, event):
|
|
1448
|
+
self.time_y_auto = self.m_check_time_y_auto.GetValue()
|
|
1449
|
+
self.m_text_time_ylim.Enable(not self.time_y_auto)
|
|
1450
|
+
self._persist_plot_options()
|
|
1451
|
+
self.init_plot()
|
|
1452
|
+
self.figure_update()
|
|
1453
|
+
self.init_merged_plot()
|
|
1454
|
+
self.figure_update_merged()
|
|
1455
|
+
|
|
1456
|
+
def on_psd_y_auto_toggle(self, event):
|
|
1457
|
+
self.psd_y_auto = self.m_check_psd_y_auto.GetValue()
|
|
1458
|
+
self.m_text_psd_ylim.Enable(not self.psd_y_auto)
|
|
1459
|
+
self._persist_plot_options()
|
|
1460
|
+
self.init_plot()
|
|
1461
|
+
self.figure_update()
|
|
1462
|
+
self.init_merged_plot()
|
|
1463
|
+
self.figure_update_merged()
|
|
1464
|
+
|
|
1465
|
+
def on_time_ylim_enter(self, event):
|
|
1466
|
+
lims = self._parse_ylim_text(self.m_text_time_ylim.GetValue())
|
|
1467
|
+
if lims is None:
|
|
1468
|
+
self.append_status_message("[plot] invalid time ylim (use [low, high])")
|
|
1469
|
+
return
|
|
1470
|
+
self.time_y_limits = lims
|
|
1471
|
+
self.m_text_time_ylim.SetValue(f"[{lims[0]}, {lims[1]}]")
|
|
1472
|
+
self._persist_plot_options()
|
|
1473
|
+
if not self.time_y_auto:
|
|
1474
|
+
self.init_plot()
|
|
1475
|
+
self.figure_update()
|
|
1476
|
+
self.init_merged_plot()
|
|
1477
|
+
self.figure_update_merged()
|
|
1478
|
+
|
|
1479
|
+
def on_psd_ylim_enter(self, event):
|
|
1480
|
+
lims = self._parse_ylim_text(self.m_text_psd_ylim.GetValue())
|
|
1481
|
+
if lims is None:
|
|
1482
|
+
self.append_status_message("[plot] invalid ASD ylim (use [low, high])")
|
|
1483
|
+
return
|
|
1484
|
+
self.psd_y_limits = lims
|
|
1485
|
+
self.m_text_psd_ylim.SetValue(f"[{lims[0]}, {lims[1]}]")
|
|
1486
|
+
self._persist_plot_options()
|
|
1487
|
+
if not self.psd_y_auto:
|
|
1488
|
+
self.init_plot()
|
|
1489
|
+
self.figure_update()
|
|
1490
|
+
self.init_merged_plot()
|
|
1491
|
+
self.figure_update_merged()
|
|
1492
|
+
|
|
1493
|
+
def _read_channel_selection(self):
|
|
1494
|
+
if hasattr(self, "m_check_chx"):
|
|
1495
|
+
return [
|
|
1496
|
+
self.m_check_chx.GetValue(),
|
|
1497
|
+
self.m_check_chy.GetValue(),
|
|
1498
|
+
self.m_check_chz.GetValue(),
|
|
1499
|
+
]
|
|
1500
|
+
if hasattr(self, "m_menu_view_chsel_x"):
|
|
1501
|
+
return [
|
|
1502
|
+
self.m_menu_view_chsel_x.IsChecked(),
|
|
1503
|
+
self.m_menu_view_chsel_y.IsChecked(),
|
|
1504
|
+
self.m_menu_view_chsel_z.IsChecked(),
|
|
1505
|
+
]
|
|
1506
|
+
return list(self.channel_selection)
|
|
1507
|
+
|
|
1508
|
+
def onChannelToggled(self, event):
|
|
1509
|
+
self.channel_selection = self._read_channel_selection()
|
|
1510
|
+
self._persist_plot_options()
|
|
1147
1511
|
|
|
1148
1512
|
self.init_plot()
|
|
1149
1513
|
self.figure_update()
|
|
@@ -1210,13 +1574,17 @@ class View(wx.Frame):
|
|
|
1210
1574
|
event.Skip()
|
|
1211
1575
|
|
|
1212
1576
|
def onTimeSpanChange(self, event):
|
|
1213
|
-
|
|
1214
|
-
|
|
1577
|
+
ts = None
|
|
1578
|
+
if hasattr(self, "m_choice_timespan"):
|
|
1579
|
+
idx = self.m_choice_timespan.GetSelection()
|
|
1580
|
+
if idx != wx.NOT_FOUND and hasattr(self, "timespan_choices"):
|
|
1581
|
+
ts = self.timespan_choices[idx]
|
|
1215
1582
|
if ts:
|
|
1216
1583
|
logger.info(f"Timespan selected: {ts} sec")
|
|
1217
1584
|
self.timespan_length = ts
|
|
1218
1585
|
if self.model is not None:
|
|
1219
1586
|
self.model.timespan_length = ts
|
|
1587
|
+
self._persist_plot_options()
|
|
1220
1588
|
self.init_plot()
|
|
1221
1589
|
self.figure_update()
|
|
1222
1590
|
self.init_merged_plot()
|
|
@@ -1228,6 +1596,6 @@ class View(wx.Frame):
|
|
|
1228
1596
|
def _append_status_message(self, msg: str) -> None:
|
|
1229
1597
|
if hasattr(self, "m_statusBar1") and self.m_statusBar1 is not None:
|
|
1230
1598
|
self.m_statusBar1.SetStatusText(msg)
|
|
1231
|
-
|
|
1599
|
+
logger.info("%s", msg)
|
|
1232
1600
|
|
|
1233
1601
|
|