diff --git a/resource/plot.ui b/resource/plot.ui index 8419a72..4b8704f 100644 --- a/resource/plot.ui +++ b/resource/plot.ui @@ -20,7 +20,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -28,7 +37,16 @@ 0 - + + 5 + + + 5 + + + 5 + + 5 @@ -101,7 +119,7 @@ - + 0 @@ -118,7 +136,32 @@ - true + false + + + + + + + Data refresh rate + + + hz + + + 0 + + + 1.000000000000000 + + + 150.000000000000000 + + + QAbstractSpinBox::DefaultStepType + + + 50.000000000000000 diff --git a/src/rqt_plot/data_plot/__init__.py b/src/rqt_plot/data_plot/__init__.py index 3fac3d9..6421a47 100644 --- a/src/rqt_plot/data_plot/__init__.py +++ b/src/rqt_plot/data_plot/__init__.py @@ -237,6 +237,7 @@ def save_settings(self, plugin_settings, instance_settings): ylim = [float(y) for y in ylim] instance_settings.set_value('x_limits', pack(xlim)) instance_settings.set_value('y_limits', pack(ylim)) + self._data_plot_widget.save_settings(plugin_settings, instance_settings) def restore_settings(self, plugin_settings, instance_settings): """Restore the settings for this widget @@ -259,6 +260,7 @@ def restore_settings(self, plugin_settings, instance_settings): self.set_ylim(ylim) except: qWarning("Failed to restore Y limits") + self._data_plot_widget.restore_settings(plugin_settings, instance_settings) def doSettingsDialog(self): """Present the user with a dialog for choosing the plot backend diff --git a/src/rqt_plot/data_plot/mat_data_plot.py b/src/rqt_plot/data_plot/mat_data_plot.py index 2959a46..64cb63c 100644 --- a/src/rqt_plot/data_plot/mat_data_plot.py +++ b/src/rqt_plot/data_plot/mat_data_plot.py @@ -198,3 +198,9 @@ def get_xlim(self): def get_ylim(self): return list(self._canvas.axes.get_ybound()) + + def save_settings(self, plugin_settings, instance_settings): + pass + + def restore_settings(self, plugin_settings, instance_settings): + pass diff --git a/src/rqt_plot/data_plot/pyqtgraph_data_plot.py b/src/rqt_plot/data_plot/pyqtgraph_data_plot.py index b157c3e..ddad215 100644 --- a/src/rqt_plot/data_plot/pyqtgraph_data_plot.py +++ b/src/rqt_plot/data_plot/pyqtgraph_data_plot.py @@ -32,7 +32,7 @@ from python_qt_binding.QtCore import Slot, Qt, qVersion, qWarning, Signal from python_qt_binding.QtGui import QColor -from python_qt_binding.QtWidgets import QVBoxLayout, QWidget +from python_qt_binding.QtWidgets import QAction, QSpinBox, QVBoxLayout, QWidget if qVersion().startswith('5.'): try: @@ -69,6 +69,8 @@ def __init__(self, parent=None): self._plot_widget.getPlotItem().addLegend() self._plot_widget.setBackground((255, 255, 255)) self._plot_widget.setXRange(0, 10, padding=0) + self._line_width = 1 + self._add_line_width_menu_option() vbox = QVBoxLayout() vbox.addWidget(self._plot_widget) self.setLayout(vbox) @@ -78,7 +80,7 @@ def __init__(self, parent=None): self._current_vline = None def add_curve(self, curve_id, curve_name, curve_color=QColor(Qt.blue), markers_on=False): - pen = mkPen(curve_color, width=1) + pen = mkPen(curve_color, width=self._line_width) symbol = "o" symbolPen = mkPen(QColor(Qt.black)) symbolBrush = mkBrush(curve_color) @@ -106,6 +108,21 @@ def _update_legend(self): if self._current_vline: self._plot_widget.addItem(self._current_vline) + def _add_line_width_menu_option(self): + menu = self._plot_widget.getMenu().addMenu('Line Width') + menu.setLayout(QVBoxLayout()) + self._line_width_spinbox = QSpinBox() + self._line_width_spinbox.setRange(1, 30) + self._line_width_spinbox.valueChanged.connect(self._line_width_spinbox_valueChanged) + menu.layout().addWidget(self._line_width_spinbox) + + @Slot(int) + def _line_width_spinbox_valueChanged(self, val): + self._line_width = val + for curve in self._curves.values(): + color = curve.opts['pen'].color() + curve.setPen(mkPen(color, width=self._line_width)) + def redraw(self): pass @@ -132,3 +149,17 @@ def get_xlim(self): def get_ylim(self): _, y_range = self._plot_widget.viewRange() return y_range + + def save_settings(self, plugin_settings, instance_settings): + instance_settings.set_value('plot_widget_state', self._plot_widget.saveState()) + instance_settings.set_value('qt_line_width', self._line_width) + + def restore_settings(self, plugin_settings, instance_settings): + plot_widget_state = instance_settings.value('plot_widget_state') + if plot_widget_state is not None: + self._plot_widget.restoreState(plot_widget_state) + + qt_line_width = instance_settings.value('qt_line_width') + if qt_line_width is not None: + self._line_width = int(qt_line_width) + self._line_width_spinbox.setValue(self._line_width) diff --git a/src/rqt_plot/data_plot/qwt_data_plot.py b/src/rqt_plot/data_plot/qwt_data_plot.py index 1e9e418..b5985c0 100644 --- a/src/rqt_plot/data_plot/qwt_data_plot.py +++ b/src/rqt_plot/data_plot/qwt_data_plot.py @@ -247,6 +247,12 @@ def get_xlim(self): def get_ylim(self): return self._y_limits + def save_settings(self, plugin_settings, instance_settings): + pass + + def restore_settings(self, plugin_settings, instance_settings): + pass + if __name__ == '__main__': from python_qt_binding.QtGui import QApplication diff --git a/src/rqt_plot/plot.py b/src/rqt_plot/plot.py index 3ccb0ec..b8cc3d6 100644 --- a/src/rqt_plot/plot.py +++ b/src/rqt_plot/plot.py @@ -148,12 +148,20 @@ def save_settings(self, plugin_settings, instance_settings): self._data_plot.save_settings(plugin_settings, instance_settings) instance_settings.set_value('autoscroll', self._widget.autoscroll_checkbox.isChecked()) instance_settings.set_value('topics', pack(self._widget._rosdata.keys())) + instance_settings.set_value("refresh_rate", self._widget.refresh_rate_spinbox.value()) def restore_settings(self, plugin_settings, instance_settings): autoscroll = instance_settings.value('autoscroll', True) in [True, 'true'] self._widget.autoscroll_checkbox.setChecked(autoscroll) self._data_plot.autoscroll(autoscroll) + refresh_rate = instance_settings.value("refresh_rate") + if refresh_rate is not None: + try: + self._widget.refresh_rate_spinbox.setValue(float(refresh_rate)) + except ValueError: + pass + self._update_title() if len(self._widget._rosdata.keys()) == 0 and not self._args.start_empty: @@ -164,6 +172,7 @@ def restore_settings(self, plugin_settings, instance_settings): self._data_plot.restore_settings(plugin_settings, instance_settings) + def trigger_configuration(self): self._data_plot.doSettingsDialog() self._update_title() diff --git a/src/rqt_plot/plot_widget.py b/src/rqt_plot/plot_widget.py index 1bf8253..deb5d9a 100644 --- a/src/rqt_plot/plot_widget.py +++ b/src/rqt_plot/plot_widget.py @@ -33,6 +33,7 @@ import os import re import time +from typing import Optional from ament_index_python.resources import get_resource from python_qt_binding import loadUi @@ -175,7 +176,6 @@ def is_plottable(node, topic_name): class PlotWidget(QWidget): - _redraw_interval = 40 def __init__(self, node, initial_topics=None, start_paused=False): super(PlotWidget, self).__init__() @@ -189,13 +189,12 @@ def __init__(self, node, initial_topics=None, start_paused=False): loadUi(ui_file, self) self.subscribe_topic_button.setIcon(QIcon.fromTheme('list-add')) self.remove_topic_button.setIcon(QIcon.fromTheme('list-remove')) - self.pause_button.setIcon(QIcon.fromTheme('media-playback-pause')) + self.play_pause_button.setIcon(QIcon.fromTheme('media-playback-pause')) self.clear_button.setIcon(QIcon.fromTheme('edit-clear')) self.data_plot = None self.subscribe_topic_button.setEnabled(False) - if start_paused: - self.pause_button.setChecked(True) + self._paused = start_paused self._topic_completer = TopicCompleter(self.topic_edit) self._topic_completer.update_topics(node) @@ -208,9 +207,11 @@ def __init__(self, node, initial_topics=None, start_paused=False): # init and start update timer for plot self._update_plot_timer = QTimer(self) self._update_plot_timer.timeout.connect(self.update_plot) + if not self._paused: + self._set_play() def switch_data_plot_widget(self, data_plot): - self.enable_timer(enabled=False) + self._set_pause() self.data_plot_layout.removeWidget(self.data_plot) if self.data_plot is not None: @@ -289,8 +290,11 @@ def on_subscribe_topic_button_clicked(self): self.add_topic(str(self.topic_edit.text())) @Slot(bool) - def on_pause_button_clicked(self, checked): - self.enable_timer(not checked) + def on_play_pause_button_clicked(self, checked): + if self._paused: + self._set_play() + else: + self._set_pause() @Slot(bool) def on_autoscroll_checkbox_clicked(self, checked): @@ -302,6 +306,13 @@ def on_autoscroll_checkbox_clicked(self, checked): def on_clear_button_clicked(self): self.clear_plot() + @Slot(float) + def on_refresh_rate_spinbox_valueChanged(self, val): + if not self._paused: + # If we are playing, then pause and re-play with the new interval + self._set_pause() + self._set_play(val) + def update_plot(self): if self.data_plot is not None: needs_redraw = False @@ -318,9 +329,13 @@ def update_plot(self): def _subscribed_topics_changed(self): self._update_remove_topic_menu() - if not self.pause_button.isChecked(): + + if self._paused and self._rosdata: # if pause button is not pressed, enable timer based on subscribed topics - self.enable_timer(self._rosdata) + self._set_play() + elif not self._paused and not self._rosdata: + self._set_pause() + self.data_plot.redraw() def _update_remove_topic_menu(self): @@ -378,8 +393,21 @@ def clean_up_subscribers(self): self._subscribed_topics_changed() - def enable_timer(self, enabled=True): - if enabled: - self._update_plot_timer.start(self._redraw_interval) - else: - self._update_plot_timer.stop() + def _set_pause(self): + # pause subscriptions + self._paused = True + # Stop timer + self._update_plot_timer.stop() + # Set button to 'play' + self.play_pause_button.setIcon(QIcon.fromTheme("media-playback-start")) + + def _set_play(self, refresh_rate: Optional[float] = None): + if refresh_rate is None: + refresh_rate = self.refresh_rate_spinbox.value() + # play subscriptions + self._paused = False + # Set button to 'pause' + self.play_pause_button.setIcon(QIcon.fromTheme("media-playback-pause")) + # Start timer again. Input is in milliseconds as int. + period_in_ms = 1000.0 / refresh_rate + self._update_plot_timer.start(period_in_ms)