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)