Files
templates/python/set_devices/qt_ports/viewport_navigation.py

83 lines
3.9 KiB
Python

"""Debounced viewport statistics shared by desktop charts."""
import time
from dataclasses import replace
try:
from PySide6.QtCore import QObject, QTimer, QThreadPool, Qt
except ImportError:
from PySide2.QtCore import QObject, QTimer, QThreadPool, Qt
from .plot_workers import NavigationSignals as _NavigationSignals, NavigationJob as _NavigationJob
from set_devices.oscilloscope import time_label
def number(value):
return f"{value:g}"
class ViewportNavigation(QObject):
def __init__(self, chart):
super().__init__(chart)
self.chart = chart
self._navigation_revision = 0
self._navigation_busy = False
self._navigation_emitter = _NavigationSignals(self)
self._navigation_emitter.ready.connect(self._navigation_ready, Qt.ConnectionType.QueuedConnection)
self._navigation_timer = QTimer(self)
self._navigation_timer.setSingleShot(True)
self._navigation_timer.setInterval(180)
self._navigation_timer.timeout.connect(self._navigation_finished)
def _navigation_changed(self):
self._navigation_revision += 1
if max((len(s.values) for s in self.chart.plot._series), default=0) <= 20000:
self.chart._scope_measurements()
self.chart._refresh_processing()
self.chart._refresh_legend()
return
self._navigation_timer.start()
def _navigation_finished(self):
if False:
return
if max((len(s.values) for s in self.chart.plot._series), default=0) <= 20000:
self.chart._scope_measurements()
self.chart._refresh_processing()
return
if self._navigation_busy:
self._navigation_timer.start()
return
self._navigation_busy = True
bounds = self.chart.plot.visible_bounds
panel = self.chart.processing
offset = self.chart._plot_time_offset if not self.chart.fft.isChecked() else 0.
# Navigation changes the next calculation's domain; retained curves keep
# their original domain. Avoid preparing millions of inputs on the UI.
panel._snapshot = replace(panel._snapshot, x_range=(bounds.left + offset, bounds.right + offset))
series = [(s.key, s.timestamps, s.values) for s in self.chart.plot._series if not s.discrete]
QThreadPool.globalInstance().start(_NavigationJob(self._navigation_revision, series, bounds,
self._navigation_emitter))
def _navigation_ready(self, revision, stats, bounds):
self._navigation_busy = False
if False or revision != self._navigation_revision:
return
for row in range(self.chart.legend.rowCount()):
item = self.chart.legend.item(row, 0)
if item is None:
continue
result, _ = stats.get(item.data(Qt.ItemDataRole.UserRole), (None, 0))
for column, key in ((4, "rms"), (5, "maximum"), (6, "minimum")):
cell = self.chart.legend.item(row, column)
if cell is not None:
cell.setText("—" if result is None else "%g" % result[key])
scope = self.chart.scope
result, count = stats.get(scope.channel.currentData(), (None, 0))
if result is None:
scope.readings.setText("Измерения: —")
return
text = " · ".join(f"{title} {number(result[key])}" for title, key in (
("Min", "minimum"), ("Max", "maximum"), ("Размах", "peak_to_peak"),
("Среднее", "mean"), ("RMS", "rms")))
if not self.chart.fft.isChecked():
frequency = result["frequency"]
text += " · f ≈ " + (f"{number(frequency)} Гц" if frequency is not None else "—")
text += f" · {time_label((bounds.right - bounds.left) / 10)}/дел"
scope.readings.setText(text + f" · N={count}")