diff --git a/package.xml b/package.xml
index d5b20d9..a444632 100644
--- a/package.xml
+++ b/package.xml
@@ -17,9 +17,6 @@
Mabel Zhang
Scott K Logan
- qt-base-dev
- libqtwidgets
-
ament_index_python
python_qt_binding
python3-matplotlib
diff --git a/src/rqt_plot/data_plot/__init__.py b/src/rqt_plot/data_plot/__init__.py
index 387dd44..4959041 100644
--- a/src/rqt_plot/data_plot/__init__.py
+++ b/src/rqt_plot/data_plot/__init__.py
@@ -29,7 +29,6 @@
import numpy
-from python_qt_binding import QT_BINDING
from python_qt_binding.QtCore import qWarning, Signal
from python_qt_binding.QtGui import QColor, QColorConstants
from python_qt_binding.QtWidgets import QHBoxLayout, QWidget
@@ -46,11 +45,6 @@
except ImportError:
MatDataPlot = None
-try:
- from .qwt_data_plot import QwtDataPlot
-except ImportError:
- QwtDataPlot = None
-
# separate class for DataPlot exceptions, just so that users can differentiate
# errors from the DataPlot widget from exceptions generated by the underlying
# libraries
@@ -66,7 +60,7 @@ class DataPlot(QWidget):
The DataPlot widget displays a plot, on one of several plotting backends,
depending on which backend(s) are available at runtime. It currently
- supports PyQtGraph, MatPlot and QwtPlot backends.
+ supports PyQtGraph and MatPlot backends.
The DataPlot widget manages the plot backend internally, and can save
and restore the internal state using `save_settings` and `restore_settings`
@@ -93,14 +87,6 @@ class DataPlot(QWidget):
'PySide: PySide > 1.1.0\n',
'enabled': MatDataPlot is not None,
},
- {
- 'title': 'QwtPlot',
- 'widget_class': QwtDataPlot,
- 'description':
- 'Based on QwtPlot\n- does not use timestamps\n- uses least CPU\n- needs Python '
- 'Qwt bindings\n',
- 'enabled': QwtDataPlot is not None,
- },
]
# pre-defined colors:
@@ -148,13 +134,9 @@ def __init__(self, parent=None):
enabled_plot_types = [pt for pt in self.plot_types if pt['enabled']]
if not enabled_plot_types:
- # minimum matplotlib version for Qt 5
- version_info = '1.4.0'
- if QT_BINDING == 'pyside':
- version_info += ' and PySide 2.0.0'
raise RuntimeError(
- 'No usable plot type found. Install at least one of: PyQtGraph, MatPlotLib '
- f'(at least {version_info}) or Python-Qwt5.')
+ 'No usable plot type found. '
+ 'Install at least one of: PyQtGraph, MatPlotLib.')
self._switch_data_plot_widget(self._plot_index)
diff --git a/src/rqt_plot/data_plot/mat_data_plot.py b/src/rqt_plot/data_plot/mat_data_plot.py
index c8adbfb..8df322f 100644
--- a/src/rqt_plot/data_plot/mat_data_plot.py
+++ b/src/rqt_plot/data_plot/mat_data_plot.py
@@ -29,42 +29,18 @@
import operator
import matplotlib
-from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
-from matplotlib.figure import Figure
-
-from python_qt_binding import QT_BINDING, QT_BINDING_VERSION, QtWidgets
-
-try:
- from pkg_resources import parse_version
-except ImportError:
- import re
-
- def parse_version(s):
- return [int(x) for x in re.sub(r'(\.0+)*$', '', s).split('.')]
-
-if QT_BINDING == 'pyside':
- qt_binding_version = QT_BINDING_VERSION.replace('~', '-')
- if parse_version(qt_binding_version) <= parse_version('1.1.2'):
- raise ImportError('A PySide version newer than 1.1.0 is required.')
-
-from python_qt_binding.QtCore import Signal
-from python_qt_binding.QtGui import QColorConstants
-from python_qt_binding.QtWidgets import QVBoxLayout, QWidget
-
-if QT_BINDING == 'pyside':
- if parse_version(matplotlib.__version__) < parse_version('2.1.0'):
- raise ImportError('A newer matplotlib is required (at least 2.1.0 for PySide 2)')
-if parse_version(matplotlib.__version__) < parse_version('1.4.0'):
- raise ImportError('A newer matplotlib is required (at least 1.4.0 for Qt 5)')
-try:
- matplotlib.use('Qt5Agg')
- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
-except ImportError:
- # work around bug in dateutil
- import sys
- import thread
- sys.modules['_thread'] = thread
- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
+matplotlib.use('QtAgg')
+
+from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas # noqa: E402
+from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar # noqa: E402,E501
+from matplotlib.figure import Figure # noqa: E402
+
+from packaging.version import Version # noqa: E402
+
+from python_qt_binding import QtWidgets # noqa: E402
+from python_qt_binding.QtCore import Signal # noqa: E402
+from python_qt_binding.QtGui import QColorConstants # noqa: E402
+from python_qt_binding.QtWidgets import QVBoxLayout, QWidget # noqa: E402
class MatDataPlot(QWidget):
@@ -101,7 +77,7 @@ def safe_tight_layout(self):
return
self.figure.tight_layout()
except ValueError:
- if parse_version(matplotlib.__version__) >= parse_version('2.2.3'):
+ if Version(matplotlib.__version__) >= Version('2.2.3'):
raise
limits_changed = Signal()
diff --git a/src/rqt_plot/data_plot/pyqtgraph_data_plot.py b/src/rqt_plot/data_plot/pyqtgraph_data_plot.py
index 72cc72e..0800788 100644
--- a/src/rqt_plot/data_plot/pyqtgraph_data_plot.py
+++ b/src/rqt_plot/data_plot/pyqtgraph_data_plot.py
@@ -32,26 +32,6 @@
from python_qt_binding.QtGui import QColor, QColorConstants
from python_qt_binding.QtWidgets import QVBoxLayout, QWidget
-try:
- from pkg_resources import parse_version
-except ImportError:
- import re
-
- def parse_version(s):
- return [int(x) for x in re.sub(r'(\.0+)*$', '', s).split('.')]
-
-try:
- from pyqtgraph import __version__ as pyqtgraph_version
-except RuntimeError:
- # pyqtgraph < 1.0 using Qt4 failing on 16.04 because kinetic uses Qt5.
- # This raises RuntimeError('the PyQt4.QtCore and PyQt5.QtCore modules both
- # wrap the QObject class')
- import pkg_resources
- pyqtgraph_version = pkg_resources.get_distribution('pyqtgraph').version
-
-if parse_version(pyqtgraph_version) < parse_version('0.10.0'):
- raise ImportError('A newer PyQtGraph version is required (at least 0.10 for Qt 5)')
-
class PyQtGraphDataPlot(QWidget):
diff --git a/src/rqt_plot/data_plot/qwt_data_plot.py b/src/rqt_plot/data_plot/qwt_data_plot.py
deleted file mode 100644
index 38c1bfe..0000000
--- a/src/rqt_plot/data_plot/qwt_data_plot.py
+++ /dev/null
@@ -1,253 +0,0 @@
-# Copyright (c) 2011, Dorian Scholz, TU Darmstadt
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#
-# * Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# * Neither the name of the TU Darmstadt nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-
-# -*- coding: utf-8 -*-
-import math
-import sys
-
-from python_qt_binding.QtCore import QEvent, QPointF, QSize, Qt, qWarning, Signal
-from python_qt_binding.QtGui import QBrush, QColorConstants, QPen, QVector2D
-import Qwt
-
-
-# create real QwtDataPlot class
-class QwtDataPlot(Qwt.QwtPlot):
- mouseCoordinatesChanged = Signal(QPointF)
- _num_value_saved = 1000
- _num_values_ploted = 1000
-
- limits_changed = Signal()
-
- def __init__(self, *args):
- super().__init__(*args)
- self.setCanvasBackground(QColorConstants.White)
- self.insertLegend(Qwt.QwtLegend(), Qwt.QwtPlot.BottomLegend)
-
- self._curves = {}
-
- # TODO: rejigger these internal data structures so that they're easier
- # to work with, and easier to use with set_xlim and set_ylim
- # this may also entail rejiggering the _time_axis so that it's
- # actually time axis data, or just isn't required any more
- # new data structure
- self._x_limits = [0.0, 10.0]
- self._y_limits = [0.0, 10.0]
-
- # old data structures
- self._last_canvas_x = 0
- self._last_canvas_y = 0
- self._pressed_canvas_y = 0
- self._pressed_canvas_x = 0
- self._last_click_coordinates = None
-
- marker_axis_y = Qwt.QwtPlotMarker()
- marker_axis_y.setLabelAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop)
- marker_axis_y.setLineStyle(Qwt.QwtPlotMarker.HLine)
- marker_axis_y.setYValue(0.0)
- marker_axis_y.attach(self)
-
- self._picker = Qwt.QwtPlotPicker(
- Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.PolygonSelection,
- Qwt.QwtPlotPicker.PolygonRubberBand, Qwt.QwtPicker.AlwaysOn, self.canvas()
- )
- self._picker.setRubberBandPen(QPen(QColorConstants.Blue))
- self._picker.setTrackerPen(QPen(QColorConstants.Blue))
-
- # Initialize data
- self.rescale()
- # self.move_canvas(0, 0)
- self.canvas().setMouseTracking(True)
- self.canvas().installEventFilter(self)
-
- def eventFilter(self, _, event):
- if event.type() == QEvent.MouseButtonRelease:
- x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x())
- y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y())
- self._last_click_coordinates = QPointF(x, y)
- self.limits_changed.emit()
- elif event.type() == QEvent.MouseMove:
- x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x())
- y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y())
- coords = QPointF(x, y)
- if self._picker.isActive() and self._last_click_coordinates is not None:
- toolTip = 'origin x: %.5f, y: %.5f' % (
- self._last_click_coordinates.x(), self._last_click_coordinates.y())
- delta = coords - self._last_click_coordinates
- toolTip += '\ndelta x: %.5f, y: %.5f\nlength: %.5f' % (
- delta.x(), delta.y(), QVector2D(delta).length())
- else:
- toolTip = 'buttons\nleft: measure\nmiddle: move\nright: zoom x/y\nwheel: zoom y'
- self.setToolTip(toolTip)
- self.mouseCoordinatesChanged.emit(coords)
- return False
-
- def log(self, level, message):
- # self.emit(SIGNAL('logMessage'), level, message)
- pass
-
- def resizeEvent(self, event):
- Qwt.QwtPlot.resizeEvent(self, event)
- self.rescale()
-
- def add_curve(self, curve_id, curve_name, curve_color=QColorConstants.Blue, markers_on=False):
- curve_id = str(curve_id)
- if curve_id in self._curves:
- return
- curve_object = Qwt.QwtPlotCurve(curve_name)
- curve_object.attach(self)
- curve_object.setPen(curve_color)
- if markers_on:
- curve_object.setSymbol(
- Qwt.QwtSymbol(
- Qwt.QwtSymbol.Ellipse, QBrush(curve_color), QPen(Qt.black), QSize(4, 4)))
- self._curves[curve_id] = curve_object
-
- def remove_curve(self, curve_id):
- curve_id = str(curve_id)
- if curve_id in self._curves:
- self._curves[curve_id].hide()
- self._curves[curve_id].attach(None)
- del self._curves[curve_id]
-
- def set_values(self, curve_id, data_x, data_y):
- curve = self._curves[curve_id]
- curve.setData(data_x, data_y)
-
- def redraw(self):
- self.replot()
-
- # ----------------------------------------------
- # begin qwtplot internal methods
- # ----------------------------------------------
- def rescale(self):
- self.setAxisScale(Qwt.QwtPlot.yLeft,
- self._y_limits[0],
- self._y_limits[1])
- self.setAxisScale(Qwt.QwtPlot.xBottom,
- self._x_limits[0],
- self._x_limits[1])
-
- self._canvas_display_height = self._y_limits[1] - self._y_limits[0]
- self._canvas_display_width = self._x_limits[1] - self._x_limits[0]
- self.redraw()
-
- def rescale_axis_x(self, delta__x):
- """Add delta_x to the length of the x axis."""
- x_width = self._x_limits[1] - self._x_limits[0]
- x_width += delta__x
- self._x_limits[1] = x_width + self._x_limits[0]
- self.rescale()
-
- def scale_axis_y(self, max_value):
- """Set the y axis height to max_value, about the current center."""
- canvas_display_height = max_value
- canvas_offset_y = (self._y_limits[1] + self._y_limits[0]) / 2.0
- y_lower_limit = canvas_offset_y - (canvas_display_height / 2)
- y_upper_limit = canvas_offset_y + (canvas_display_height / 2)
- self._y_limits = [y_lower_limit, y_upper_limit]
- self.rescale()
-
- def move_canvas(self, delta_x, delta_y):
- """Move the canvas by delta_x and delta_y in SCREEN COORDINATES."""
- canvas_offset_x = delta_x * self._canvas_display_width / self.canvas().width()
- canvas_offset_y = delta_y * self._canvas_display_height / self.canvas().height()
- self._x_limits = [limit + canvas_offset_x for limit in self._x_limits]
- self._y_limits = [limit + canvas_offset_y for limit in self._y_limits]
- self.rescale()
-
- def mousePressEvent(self, event):
- self._last_canvas_x = event.x() - self.canvas().x()
- self._last_canvas_y = event.y() - self.canvas().y()
- self._pressed_canvas_y = event.y() - self.canvas().y()
-
- def mouseMoveEvent(self, event):
- canvas_x = event.x() - self.canvas().x()
- canvas_y = event.y() - self.canvas().y()
- if event.buttons() & Qt.MiddleButton: # middle button moves the canvas
- delta_x = self._last_canvas_x - canvas_x
- delta_y = canvas_y - self._last_canvas_y
- self.move_canvas(delta_x, delta_y)
- elif event.buttons() & Qt.RightButton: # right button zooms
- zoom_factor = max(-0.6, min(0.6, (self._last_canvas_y - canvas_y) / 20.0 / 2.0))
- delta_y = (self.canvas().height() / 2.0) - self._pressed_canvas_y
- self.move_canvas(0, zoom_factor * delta_y * 1.0225)
- self.scale_axis_y(
- max(0.005,
- self._canvas_display_height - (zoom_factor * self._canvas_display_height)))
- self.rescale_axis_x(self._last_canvas_x - canvas_x)
- self._last_canvas_x = canvas_x
- self._last_canvas_y = canvas_y
-
- def wheelEvent(self, event): # mouse wheel zooms the y-axis
- # y position of pointer in graph coordinates
- canvas_y = event.y() - self.canvas().y()
-
- try:
- delta = event.angleDelta().y()
- except AttributeError:
- delta = event.delta()
- zoom_factor = max(-0.6, min(0.6, (delta / 120) / 6.0))
- delta_y = (self.canvas().height() / 2.0) - canvas_y
- self.move_canvas(0, zoom_factor * delta_y * 1.0225)
-
- self.scale_axis_y(
- max(0.0005, self._canvas_display_height - zoom_factor * self._canvas_display_height))
- self.limits_changed.emit()
-
- def vline(self, x, color):
- qWarning('QwtDataPlot.vline is not implemented yet')
-
- def set_xlim(self, limits):
- self.setAxisScale(Qwt.QwtPlot.xBottom, limits[0], limits[1])
- self._x_limits = limits
-
- def set_ylim(self, limits):
- self.setAxisScale(Qwt.QwtPlot.yLeft, limits[0], limits[1])
- self._y_limits = limits
-
- def get_xlim(self):
- return self._x_limits
-
- def get_ylim(self):
- return self._y_limits
-
-
-if __name__ == '__main__':
- from python_qt_binding.QtGui import QApplication
-
- app = QApplication(sys.argv)
- plot = QwtDataPlot()
- plot.resize(700, 500)
- plot.show()
- plot.add_curve(0, '(x/500)^2')
- plot.add_curve(1, 'sin(x / 20) * 500')
- for i in range(plot._num_value_saved):
- plot.update_value(0, (i / 500.0) * (i / 5.0))
- plot.update_value(1, math.sin(i / 20.0) * 500)
-
- sys.exit(app.exec())
diff --git a/src/rqt_plot/plot_widget.py b/src/rqt_plot/plot_widget.py
index c7238ea..8eaf4a8 100644
--- a/src/rqt_plot/plot_widget.py
+++ b/src/rqt_plot/plot_widget.py
@@ -31,15 +31,9 @@
import time
from ament_index_python.resources import get_resource
-from packaging.version import Version
from python_qt_binding import loadUi
-from python_qt_binding import QT_BINDING_VERSION
from python_qt_binding.QtCore import Qt, QTimer, qWarning, Slot
-if Version(QT_BINDING_VERSION) >= Version('6.0.0'):
- from python_qt_binding.QtGui import QAction
-else:
- from python_qt_binding.QtWidgets import QAction
-from python_qt_binding.QtGui import QIcon
+from python_qt_binding.QtGui import QAction, QIcon
from python_qt_binding.QtWidgets import QMenu, QWidget
from rosidl_parser.definition import AbstractGenericString