mat_data_plot.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 # Copyright (c) 2011, Ye Cheng, Dorian Scholz
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of the TU Darmstadt nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 
33 from python_qt_binding import QT_BINDING, QT_BINDING_VERSION
34 
35 try:
36  from pkg_resources import parse_version
37 except:
38  import re
39 
40  def parse_version(s):
41  return [int(x) for x in re.sub(r'(\.0+)*$', '', s).split('.')]
42 
43 if QT_BINDING == 'pyside':
44  qt_binding_version = QT_BINDING_VERSION.replace('~', '-')
45  if parse_version(qt_binding_version) <= parse_version('1.1.2'):
46  raise ImportError('A PySide version newer than 1.1.0 is required.')
47 
48 from python_qt_binding.QtCore import Slot, Qt, qVersion, qWarning, Signal
49 from python_qt_binding.QtGui import QColor
50 from python_qt_binding.QtWidgets import QWidget, QVBoxLayout, QSizePolicy
51 
52 import operator
53 import matplotlib
54 if qVersion().startswith('5.'):
55  if QT_BINDING == 'pyside':
56  if parse_version(matplotlib.__version__) < parse_version('2.1.0'):
57  raise ImportError('A newer matplotlib is required (at least 2.1.0 for PySide 2)')
58  if parse_version(matplotlib.__version__) < parse_version('1.4.0'):
59  raise ImportError('A newer matplotlib is required (at least 1.4.0 for Qt 5)')
60  try:
61  matplotlib.use('Qt5Agg')
62  from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
63  except ImportError:
64  # work around bug in dateutil
65  import sys
66  import thread
67  sys.modules['_thread'] = thread
68  from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
69  from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
70 elif qVersion().startswith('4.'):
71  if parse_version(matplotlib.__version__) < parse_version('1.1.0'):
72  raise ImportError('A newer matplotlib is required (at least 1.1.0 for Qt 4)')
73  try:
74  matplotlib.use('Qt4Agg')
75  from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
76  except ImportError:
77  # work around bug in dateutil
78  import sys
79  import thread
80  sys.modules['_thread'] = thread
81  from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
82  try:
83  from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
84  except ImportError:
85  from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
86 else:
87  raise NotImplementedError('Unsupport Qt version: %s' % qVersion())
88 
89 from matplotlib.figure import Figure
90 
91 import numpy
92 
93 
94 class MatDataPlot(QWidget):
95 
96  class Canvas(FigureCanvas):
97 
98  """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
99 
100  def __init__(self, parent=None):
101  super(MatDataPlot.Canvas, self).__init__(Figure())
102  self.axes = self.figure.add_subplot(111)
103  self.axes.grid(True, color='gray')
104  self.safe_tight_layout()
105  self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
106  self.setMinimumSize(1,1)
107  self.updateGeometry()
108 
109  def resizeEvent(self, event):
110  super(MatDataPlot.Canvas, self).resizeEvent(event)
111  self.safe_tight_layout()
112 
113  def safe_tight_layout(self):
114  """
115  Deal with "ValueError: bottom cannot be >= top" bug in older matplotlib versions
116  (before v2.2.3)
117 
118  References:
119  - https://github.com/matplotlib/matplotlib/pull/10915
120  - https://github.com/ros-visualization/rqt_plot/issues/35
121  """
122  try:
123  self.figure.tight_layout()
124  except ValueError:
125  if parse_version(matplotlib.__version__) >= parse_version('2.2.3'):
126  raise
127 
128  limits_changed = Signal()
129 
130  def __init__(self, parent=None):
131  super(MatDataPlot, self).__init__(parent)
133  self._toolbar = NavigationToolbar(self._canvas, self._canvas)
134  vbox = QVBoxLayout()
135  vbox.addWidget(self._toolbar)
136  vbox.addWidget(self._canvas)
137  self.setLayout(vbox)
138 
139  self._curves = {}
140  self._current_vline = None
141  self._canvas.mpl_connect('button_release_event', self._limits_changed)
142 
143  def _limits_changed(self, event):
144  self.limits_changed.emit()
145 
146  def add_curve(self, curve_id, curve_name, curve_color=QColor(Qt.blue), markers_on=False):
147 
148  # adding an empty curve and change the limits, so save and restore them
149  x_limits = self.get_xlim()
150  y_limits = self.get_ylim()
151  if markers_on:
152  marker_size = 3
153  else:
154  marker_size = 0
155  line = self._canvas.axes.plot([], [], 'o-', markersize=marker_size, label=curve_name,
156  linewidth=1, picker=5, color=curve_color.name())[0]
157  self._curves[curve_id] = line
158  self._update_legend()
159  self.set_xlim(x_limits)
160  self.set_ylim(y_limits)
161 
162  def remove_curve(self, curve_id):
163  curve_id = str(curve_id)
164  if curve_id in self._curves:
165  self._curves[curve_id].remove()
166  del self._curves[curve_id]
167  self._update_legend()
168 
169  def _update_legend(self):
170  handles, labels = self._canvas.axes.get_legend_handles_labels()
171  if handles:
172  hl = sorted(zip(handles, labels), key=operator.itemgetter(1))
173  handles, labels = zip(*hl)
174  self._canvas.axes.legend(handles, labels, loc='upper left')
175 
176  def set_values(self, curve, data_x, data_y):
177  line = self._curves[curve]
178  line.set_data(data_x, data_y)
179 
180  def redraw(self):
181  self._canvas.axes.grid(True, color='gray')
182  self._canvas.draw()
183 
184  def vline(self, x, color):
185  # convert color range from (0,255) to (0,1.0)
186  matcolor = (color[0] / 255.0, color[1] / 255.0, color[2] / 255.0)
187  if self._current_vline:
188  self._current_vline.remove()
189  self._current_vline = self._canvas.axes.axvline(x=x, color=matcolor)
190 
191  def set_xlim(self, limits):
192  self._canvas.axes.set_xbound(lower=limits[0], upper=limits[1])
193 
194  def set_ylim(self, limits):
195  self._canvas.axes.set_ybound(lower=limits[0], upper=limits[1])
196 
197  def get_xlim(self):
198  return list(self._canvas.axes.get_xbound())
199 
200  def get_ylim(self):
201  return list(self._canvas.axes.get_ybound())
def add_curve(self, curve_id, curve_name, curve_color=QColor(Qt.blue), markers_on=False)
def set_values(self, curve, data_x, data_y)


rqt_plot
Author(s): Dorian Scholz, Dirk Thomas
autogenerated on Thu Feb 25 2021 03:12:45