qwt_data_plot.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 # Copyright (c) 2011, Dorian Scholz, TU Darmstadt
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 # -*- coding: utf-8 -*-
34 from __future__ import division
35 import math
36 import sys
37 
38 from python_qt_binding.QtCore import QEvent, QSize, QPointF, Qt, Signal, Slot, qWarning
39 from python_qt_binding.QtGui import QColor, QPen, QBrush, QVector2D
40 import Qwt
41 
42 from numpy import arange, zeros, concatenate
43 
44 
45 # create real QwtDataPlot class
46 class QwtDataPlot(Qwt.QwtPlot):
47  mouseCoordinatesChanged = Signal(QPointF)
48  _num_value_saved = 1000
49  _num_values_ploted = 1000
50 
51  limits_changed = Signal()
52 
53  def __init__(self, *args):
54  super(QwtDataPlot, self).__init__(*args)
55  self.setCanvasBackground(Qt.white)
56  self.insertLegend(Qwt.QwtLegend(), Qwt.QwtPlot.BottomLegend)
57 
58  self._curves = {}
59 
60  # TODO: rejigger these internal data structures so that they're easier
61  # to work with, and easier to use with set_xlim and set_ylim
62  # this may also entail rejiggering the _time_axis so that it's
63  # actually time axis data, or just isn't required any more
64  # new data structure
65  self._x_limits = [0.0, 10.0]
66  self._y_limits = [0.0, 10.0]
67 
68  # old data structures
69  self._last_canvas_x = 0
70  self._last_canvas_y = 0
74 
75  marker_axis_y = Qwt.QwtPlotMarker()
76  marker_axis_y.setLabelAlignment(Qt.AlignRight | Qt.AlignTop)
77  marker_axis_y.setLineStyle(Qwt.QwtPlotMarker.HLine)
78  marker_axis_y.setYValue(0.0)
79  marker_axis_y.attach(self)
80 
81  self._picker = Qwt.QwtPlotPicker(
82  Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.PolygonSelection,
83  Qwt.QwtPlotPicker.PolygonRubberBand, Qwt.QwtPicker.AlwaysOn, self.canvas()
84  )
85  self._picker.setRubberBandPen(QPen(Qt.blue))
86  self._picker.setTrackerPen(QPen(Qt.blue))
87 
88  # Initialize data
89  self.rescale()
90  # self.move_canvas(0, 0)
91  self.canvas().setMouseTracking(True)
92  self.canvas().installEventFilter(self)
93 
94  def eventFilter(self, _, event):
95  if event.type() == QEvent.MouseButtonRelease:
96  x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x())
97  y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y())
98  self._last_click_coordinates = QPointF(x, y)
99  self.limits_changed.emit()
100  elif event.type() == QEvent.MouseMove:
101  x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x())
102  y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y())
103  coords = QPointF(x, y)
104  if self._picker.isActive() and self._last_click_coordinates is not None:
105  toolTip = 'origin x: %.5f, y: %.5f' % (
106  self._last_click_coordinates.x(), self._last_click_coordinates.y())
107  delta = coords - self._last_click_coordinates
108  toolTip += '\ndelta x: %.5f, y: %.5f\nlength: %.5f' % (
109  delta.x(), delta.y(), QVector2D(delta).length())
110  else:
111  toolTip = 'buttons\nleft: measure\nmiddle: move\nright: zoom x/y\nwheel: zoom y'
112  self.setToolTip(toolTip)
113  self.mouseCoordinatesChanged.emit(coords)
114  return False
115 
116  def log(self, level, message):
117  # self.emit(SIGNAL('logMessage'), level, message)
118  pass
119 
120  def resizeEvent(self, event):
121  Qwt.QwtPlot.resizeEvent(self, event)
122  self.rescale()
123 
124  def add_curve(self, curve_id, curve_name, curve_color=QColor(Qt.blue), markers_on=False):
125  curve_id = str(curve_id)
126  if curve_id in self._curves:
127  return
128  curve_object = Qwt.QwtPlotCurve(curve_name)
129  curve_object.attach(self)
130  curve_object.setPen(curve_color)
131  if markers_on:
132  curve_object.setSymbol(
133  Qwt.QwtSymbol(Qwt.QwtSymbol.Ellipse, QBrush(curve_color), QPen(Qt.black), QSize(4, 4)))
134  self._curves[curve_id] = curve_object
135 
136  def remove_curve(self, curve_id):
137  curve_id = str(curve_id)
138  if curve_id in self._curves:
139  self._curves[curve_id].hide()
140  self._curves[curve_id].attach(None)
141  del self._curves[curve_id]
142 
143  def set_values(self, curve_id, data_x, data_y):
144  curve = self._curves[curve_id]
145  curve.setData(data_x, data_y)
146 
147  def redraw(self):
148  self.replot()
149 
150  # ----------------------------------------------
151  # begin qwtplot internal methods
152  # ----------------------------------------------
153  def rescale(self):
154  self.setAxisScale(Qwt.QwtPlot.yLeft,
155  self._y_limits[0],
156  self._y_limits[1])
157  self.setAxisScale(Qwt.QwtPlot.xBottom,
158  self._x_limits[0],
159  self._x_limits[1])
160 
161  self._canvas_display_height = self._y_limits[1] - self._y_limits[0]
162  self._canvas_display_width = self._x_limits[1] - self._x_limits[0]
163  self.redraw()
164 
165  def rescale_axis_x(self, delta__x):
166  """
167  add delta_x to the length of the x axis
168  """
169  x_width = self._x_limits[1] - self._x_limits[0]
170  x_width += delta__x
171  self._x_limits[1] = x_width + self._x_limits[0]
172  self.rescale()
173 
174  def scale_axis_y(self, max_value):
175  """
176  set the y axis height to max_value, about the current center
177  """
178  canvas_display_height = max_value
179  canvas_offset_y = (self._y_limits[1] + self._y_limits[0]) / 2.0
180  y_lower_limit = canvas_offset_y - (canvas_display_height / 2)
181  y_upper_limit = canvas_offset_y + (canvas_display_height / 2)
182  self._y_limits = [y_lower_limit, y_upper_limit]
183  self.rescale()
184 
185  def move_canvas(self, delta_x, delta_y):
186  """
187  move the canvas by delta_x and delta_y in SCREEN COORDINATES
188  """
189  canvas_offset_x = delta_x * self._canvas_display_width / self.canvas().width()
190  canvas_offset_y = delta_y * self._canvas_display_height / self.canvas().height()
191  self._x_limits = [l + canvas_offset_x for l in self._x_limits]
192  self._y_limits = [l + canvas_offset_y for l in self._y_limits]
193  self.rescale()
194 
195  def mousePressEvent(self, event):
196  self._last_canvas_x = event.x() - self.canvas().x()
197  self._last_canvas_y = event.y() - self.canvas().y()
198  self._pressed_canvas_y = event.y() - self.canvas().y()
199 
200  def mouseMoveEvent(self, event):
201  canvas_x = event.x() - self.canvas().x()
202  canvas_y = event.y() - self.canvas().y()
203  if event.buttons() & Qt.MiddleButton: # middle button moves the canvas
204  delta_x = self._last_canvas_x - canvas_x
205  delta_y = canvas_y - self._last_canvas_y
206  self.move_canvas(delta_x, delta_y)
207  elif event.buttons() & Qt.RightButton: # right button zooms
208  zoom_factor = max(-0.6, min(0.6, (self._last_canvas_y - canvas_y) / 20.0 / 2.0))
209  delta_y = (self.canvas().height() / 2.0) - self._pressed_canvas_y
210  self.move_canvas(0, zoom_factor * delta_y * 1.0225)
211  self.scale_axis_y(
212  max(0.005, self._canvas_display_height - (zoom_factor * self._canvas_display_height)))
213  self.rescale_axis_x(self._last_canvas_x - canvas_x)
214  self._last_canvas_x = canvas_x
215  self._last_canvas_y = canvas_y
216 
217  def wheelEvent(self, event): # mouse wheel zooms the y-axis
218  # y position of pointer in graph coordinates
219  canvas_y = event.y() - self.canvas().y()
220 
221  try:
222  delta = event.angleDelta().y()
223  except AttributeError:
224  delta = event.delta()
225  zoom_factor = max(-0.6, min(0.6, (delta / 120) / 6.0))
226  delta_y = (self.canvas().height() / 2.0) - canvas_y
227  self.move_canvas(0, zoom_factor * delta_y * 1.0225)
228 
229  self.scale_axis_y(
230  max(0.0005, self._canvas_display_height - zoom_factor * self._canvas_display_height))
231  self.limits_changed.emit()
232 
233  def vline(self, x, color):
234  qWarning("QwtDataPlot.vline is not implemented yet")
235 
236  def set_xlim(self, limits):
237  self.setAxisScale(Qwt.QwtPlot.xBottom, limits[0], limits[1])
238  self._x_limits = limits
239 
240  def set_ylim(self, limits):
241  self.setAxisScale(Qwt.QwtPlot.yLeft, limits[0], limits[1])
242  self._y_limits = limits
243 
244  def get_xlim(self):
245  return self._x_limits
246 
247  def get_ylim(self):
248  return self._y_limits
249 
250 
251 if __name__ == '__main__':
252  from python_qt_binding.QtGui import QApplication
253 
254  app = QApplication(sys.argv)
255  plot = QwtDataPlot()
256  plot.resize(700, 500)
257  plot.show()
258  plot.add_curve(0, '(x/500)^2')
259  plot.add_curve(1, 'sin(x / 20) * 500')
260  for i in range(plot._num_value_saved):
261  plot.update_value(0, (i / 500.0) * (i / 5.0))
262  plot.update_value(1, math.sin(i / 20.0) * 500)
263 
264  sys.exit(app.exec_())
def set_values(self, curve_id, data_x, data_y)
def add_curve(self, curve_id, curve_name, curve_color=QColor(Qt.blue), markers_on=False)
def move_canvas(self, delta_x, delta_y)


rqt_plot
Author(s): Dorian Scholz
autogenerated on Sun Mar 17 2019 02:21:35