qwt_data_plot.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 # Copyright (c) 2011, Dorian Scholz, TU Darmstadt
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #   * Redistributions of source code must retain the above copyright
00011 #     notice, this list of conditions and the following disclaimer.
00012 #   * Redistributions in binary form must reproduce the above
00013 #     copyright notice, this list of conditions and the following
00014 #     disclaimer in the documentation and/or other materials provided
00015 #     with the distribution.
00016 #   * Neither the name of the TU Darmstadt nor the names of its
00017 #     contributors may be used to endorse or promote products derived
00018 #     from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 
00033 # -*- coding: utf-8 -*-
00034 from __future__ import division
00035 import math
00036 import sys
00037 
00038 from python_qt_binding.QtCore import QEvent, QPointF, Qt, SIGNAL, Signal, Slot
00039 from python_qt_binding.QtGui import QPen, QVector2D
00040 import Qwt
00041 
00042 from numpy import arange, zeros, concatenate
00043 
00044 
00045 # create real QwtDataPlot class
00046 class QwtDataPlot(Qwt.QwtPlot):
00047     mouseCoordinatesChanged = Signal(QPointF)
00048     _colors = [Qt.red, Qt.blue, Qt.magenta, Qt.cyan, Qt.green, Qt.darkYellow, Qt.black, Qt.darkRed, Qt.gray, Qt.darkCyan]
00049     _num_value_saved = 1000
00050     _num_values_ploted = 1000
00051 
00052     def __init__(self, *args):
00053         super(QwtDataPlot, self).__init__(*args)
00054         self.setCanvasBackground(Qt.white)
00055         self.insertLegend(Qwt.QwtLegend(), Qwt.QwtPlot.BottomLegend)
00056 
00057         self._curves = {}
00058         self._data_offset_x = 0
00059         self._canvas_offset_x = 0
00060         self._canvas_offset_y = 0
00061         self._last_canvas_x = 0
00062         self._last_canvas_y = 0
00063         self._pressed_canvas_y = 0
00064         self._last_click_coordinates = None
00065         self._color_index = 0
00066 
00067         marker_axis_y = Qwt.QwtPlotMarker()
00068         marker_axis_y.setLabelAlignment(Qt.AlignRight | Qt.AlignTop)
00069         marker_axis_y.setLineStyle(Qwt.QwtPlotMarker.HLine)
00070         marker_axis_y.setYValue(0.0)
00071         marker_axis_y.attach(self)
00072 
00073         self._picker = Qwt.QwtPlotPicker(
00074             Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.PolygonSelection,
00075             Qwt.QwtPlotPicker.PolygonRubberBand, Qwt.QwtPicker.AlwaysOn, self.canvas()
00076         )
00077         self._picker.setRubberBandPen(QPen(self._colors[-1]))
00078         self._picker.setTrackerPen(QPen(self._colors[-1]))
00079 
00080         # Initialize data
00081         self._time_axis = arange(self._num_values_ploted)
00082         self._canvas_display_height = 1000
00083         self._canvas_display_width = self.canvas().width()
00084         self._data_offset_x = self._num_value_saved - len(self._time_axis)
00085         self.redraw()
00086         self.move_canvas(0, 0)
00087         self.canvas().setMouseTracking(True)
00088         self.canvas().installEventFilter(self)
00089 
00090     def eventFilter(self, _, event):
00091         if event.type() == QEvent.MouseButtonRelease:
00092             x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x())
00093             y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y())
00094             self._last_click_coordinates = QPointF(x, y)
00095         elif event.type() == QEvent.MouseMove:
00096             x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x())
00097             y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y())
00098             coords = QPointF(x, y)
00099             if self._picker.isActive() and self._last_click_coordinates is not None:
00100                 toolTip = 'origin x: %.5f, y: %.5f' % (self._last_click_coordinates.x(), self._last_click_coordinates.y())
00101                 delta = coords - self._last_click_coordinates
00102                 toolTip += '\ndelta x: %.5f, y: %.5f\nlength: %.5f' % (delta.x(), delta.y(), QVector2D(delta).length())
00103             else:
00104                 toolTip = 'buttons\nleft: measure\nmiddle: move\nright: zoom x/y\nwheel: zoom y'
00105             self.setToolTip(toolTip)
00106             self.mouseCoordinatesChanged.emit(coords)
00107         return False
00108 
00109     def log(self, level, message):
00110         self.emit(SIGNAL('logMessage'), level, message)
00111 
00112     def resizeEvent(self, event):
00113         #super(QwtDataPlot, self).resizeEvent(event)
00114         Qwt.QwtPlot.resizeEvent(self, event)
00115         self.rescale()
00116 
00117     def add_curve(self, curve_id, curve_name, values_x, values_y):
00118         curve_id = str(curve_id)
00119         if self._curves.get(curve_id):
00120             return
00121         curve_object = Qwt.QwtPlotCurve(curve_name)
00122         curve_object.attach(self)
00123         curve_object.setPen(QPen(self._colors[self._color_index % len(self._colors)]))
00124         self._color_index += 1
00125         self._curves[curve_id] = {
00126             'name': curve_name,
00127             'data': zeros(self._num_value_saved),
00128             'object': curve_object,
00129         }
00130 
00131     def remove_curve(self, curve_id):
00132         curve_id = str(curve_id)
00133         if curve_id in self._curves:
00134             self._curves[curve_id]['object'].hide()
00135             self._curves[curve_id]['object'].attach(None)
00136             del self._curves[curve_id]['object']
00137             del self._curves[curve_id]
00138 
00139     @Slot(str, list, list)
00140     def update_values(self, curve_id, values_x, values_y):
00141         for value_x, value_y in zip(values_x, values_y):
00142             self.update_value(curve_id, value_x, value_y)
00143 
00144     @Slot(str, float, float)
00145     def update_value(self, curve_id, value_x, value_y):
00146         curve_id = str(curve_id)
00147         # update data plot
00148         if curve_id in self._curves:
00149             # TODO: use value_x as timestamp
00150             self._curves[curve_id]['data'] = concatenate((self._curves[curve_id]['data'][1:], self._curves[curve_id]['data'][:1]), 1)
00151             self._curves[curve_id]['data'][-1] = float(value_y)
00152 
00153     def redraw(self):
00154         for curve_id in self._curves.keys():
00155             self._curves[curve_id]['object'].setData(self._time_axis, self._curves[curve_id]['data'][self._data_offset_x: self._data_offset_x + len(self._time_axis)])
00156             #self._curves[curve_id]['object'].setStyle(Qwt.QwtPlotCurve.CurveStyle(3))
00157         self.replot()
00158 
00159     def rescale(self):
00160         y_num_ticks = self.height() / 40
00161         y_lower_limit = self._canvas_offset_y - (self._canvas_display_height / 2)
00162         y_upper_limit = self._canvas_offset_y + (self._canvas_display_height / 2)
00163 
00164         # calculate a fitting step size for nice, round tick labels, depending on the displayed value area
00165         y_delta = y_upper_limit - y_lower_limit
00166         exponent = int(math.log10(y_delta))
00167         presicion = -(exponent - 2)
00168         y_step_size = round(y_delta / y_num_ticks, presicion)
00169 
00170         self.setAxisScale(Qwt.QwtPlot.yLeft, y_lower_limit, y_upper_limit, y_step_size)
00171 
00172         self.setAxisScale(Qwt.QwtPlot.xBottom, 0, len(self._time_axis))
00173         self.redraw()
00174 
00175     def rescale_axis_x(self, delta__x):
00176         new_len = len(self._time_axis) + delta__x
00177         new_len = max(10, min(new_len, self._num_value_saved))
00178         self._time_axis = arange(new_len)
00179         self._data_offset_x = max(0, min(self._data_offset_x, self._num_value_saved - len(self._time_axis)))
00180         self.rescale()
00181 
00182     def scale_axis_y(self, max_value):
00183         self._canvas_display_height = max_value
00184         self.rescale()
00185 
00186     def move_canvas(self, delta_x, delta_y):
00187         self._data_offset_x += delta_x * len(self._time_axis) / float(self.canvas().width())
00188         self._data_offset_x = max(0, min(self._data_offset_x, self._num_value_saved - len(self._time_axis)))
00189         self._canvas_offset_x += delta_x * self._canvas_display_width / self.canvas().width()
00190         self._canvas_offset_y += delta_y * self._canvas_display_height / self.canvas().height()
00191         self.rescale()
00192 
00193     def mousePressEvent(self, event):
00194         self._last_canvas_x = event.x() - self.canvas().x()
00195         self._last_canvas_y = event.y() - self.canvas().y()
00196         self._pressed_canvas_y = event.y() - self.canvas().y()
00197 
00198     def mouseMoveEvent(self, event):
00199         canvas_x = event.x() - self.canvas().x()
00200         canvas_y = event.y() - self.canvas().y()
00201         if event.buttons() & Qt.MiddleButton:  # middle button moves the canvas
00202             delta_x = self._last_canvas_x - canvas_x
00203             delta_y = canvas_y - self._last_canvas_y
00204             self.move_canvas(delta_x, delta_y)
00205         elif event.buttons() & Qt.RightButton:   # right button zooms
00206             zoom_factor = max(-0.6, min(0.6, (self._last_canvas_y - canvas_y) / 20.0 / 2.0))
00207             delta_y = (self.canvas().height() / 2.0) - self._pressed_canvas_y
00208             self.move_canvas(0, zoom_factor * delta_y * 1.0225)
00209             self.scale_axis_y(max(0.005, self._canvas_display_height - (zoom_factor * self._canvas_display_height)))
00210             self.rescale_axis_x(self._last_canvas_x - canvas_x)
00211         self._last_canvas_x = canvas_x
00212         self._last_canvas_y = canvas_y
00213 
00214     def wheelEvent(self, event):  # mouse wheel zooms the y-axis
00215         canvas_y = event.y() - self.canvas().y()
00216         zoom_factor = max(-0.6, min(0.6, (event.delta() / 120) / 6.0))
00217         delta_y = (self.canvas().height() / 2.0) - canvas_y
00218         self.move_canvas(0, zoom_factor * delta_y * 1.0225)
00219         self.scale_axis_y(max(0.0005, self._canvas_display_height - zoom_factor * self._canvas_display_height))
00220 
00221 
00222 if __name__ == '__main__':
00223     from python_qt_binding.QtGui import QApplication
00224 
00225     app = QApplication(sys.argv)
00226     plot = QwtDataPlot()
00227     plot.resize(700, 500)
00228     plot.show()
00229     plot.add_curve(0, '(x/500)^2')
00230     plot.add_curve(1, 'sin(x / 20) * 500')
00231     for i in range(plot._num_value_saved):
00232         plot.update_value(0, (i / 500.0) * (i / 5.0))
00233         plot.update_value(1, math.sin(i / 20.0) * 500)
00234 
00235     sys.exit(app.exec_())


rqt_plot
Author(s): Dorian Scholz
autogenerated on Fri Jan 3 2014 11:55:13