Microwave filters GUI  2.0.3
dbplot.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 
4 ## @file
5 
6 ##
7 # @package dbplot
8 # Plot data curves in multiple tabs.
9 # <p>@author J.M. Rius<br>Universitat Politècnica de Catalunya (UPC)
10 # <p>@version 1.0
11 # <p>@date November 25, 2010
12 # <p>dBplot is copyright &copy; Juan Manuel Rius 2009, <br>Universitat Politècnica de Catalunya (UPC)<br> rius@tsc.upc.edu.
13 # <p>
14 # This program uses the Qwt library for data plotting, developed by the Qwt project (http://qwt.sf.net),
15 # and pyqwt5, the python binding of qwt5 C++ library for Qt applications.
16 # <p>
17 # This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
19 # <p>
20 # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
22 # <p>
23 # You should have received a copy of the GNU General Public License along with this program in file "LICENSE.GPL3";
24 # if not, download it from http://www.gnu.org/licenses/gpl-3.0.html .
25 #
26 
27 import sys
28 import platform
29 from time import time
30 from math import log10, ceil
31 from PyQt4.QtCore import *
32 from PyQt4.QtGui import *
33 import numpy as np
34 import PyQt4.Qwt5 as Qwt
35 import Ui_dbplot
36 import Ui_dbplot_scaling
37 
38 ##
39 # Global variable useful only for MacOSX
40 MAC = "qt_mac_set_native_menubar" in dir()
41 
42 ##
43 # Application name, to be displayed in 'About' dialog
44 applicationName = 'dBplot'
45 
46 
47 ##
48 #
49 # GUI dialog to contain plot.
50 # Uses pyqwt5, the python binding of qwt5 C++ library for Qt applications.
51 #
52 class DbPlot(QMainWindow, Ui_dbplot.Ui_Plot):
53 
54  ##
55  #
56  # Constructor: Creates PyQt4.QtGui.Qdialog window containing PyQt4.Qwt5.QwtPlot widget.
57  # Uses pyqwt5, the python binding of qwt5 C++ library for Qt applications.
58  #
59  # @param XData = List of X-axis data (list of numpy arrays).
60  # Each element in the list is the x-axis data for the corresponding element in leftYData.
61  # When there is only one curve, it is not necessary to create a list of curves: XData argument can be the curve x-data alone.
62  # @param leftYData = List of curve-data to plot in left y-axis (list of numpy arrays).
63  # Curve-data can be a list of int or float or a numpy array.
64  # If a list element is a 2-D numpy array, it defines a curve family sharing the same curve properties.
65  # Curves in a curve family have Ydata in the columns of the 2-D numpy array.
66  # When there is only one curve, it is not necessary to create a list of curves: leftYData argument can be the curve-data alone.
67  # @param rightXData = List of X-axis data (list of numpy arrays).
68  # Each element in the list is the x-axis data for the corresponding element in rightYData.
69  # When there is only one curve, it is not necessary to create a list of curves: rightXData argument can be the curve x-data alone.
70  # If equal to None, XData is used both for left-y and right-y axis data.
71  # Default None.
72  # @param rightYData = List of curve-data to plot in right y-axis (list of numpy arrays).
73  # Curve-data can be a list of int or float or a numpy array.
74  # If a list element is a 2-D numpy array, it defines a curve family sharing the same curve properties.
75  # Curves in a curve family have Ydata in the columns of the 2-D numpy array.
76  # When there is only one curve, it is not necessary to create a list of curves: rightYData argument can be the curve-data alone.
77  # If it is equal to None, there is no data in the right-Y axis. Default None.
78  # @param leftYmin = Set manual scaling for left-y axis with this minimum value.
79  # @param leftYmax = Set manual scaling for left-y axis with this maximum value.
80  # @param rightYmin = Set manual scaling for right-y axis with this minimum value.
81  # @param rightYmax = Set manual scaling for right-y axis with this maximum value.
82  # @param Xmin = Set manual scaling for x axis with this minimum value.
83  # @param Xmax = Set manual scaling for x axis with this maximum value.
84  # @param windowTitle = Window title (string). Default None.
85  # @param tabTitle = Tab title (string). Default None.
86  # @param title = Plot title (string). Default None.
87  # @param leftYNames = List of names corresponding to leftYData (list of strings).
88  # If equal to None, no legend is created. Default None.
89  #
90  # @param rightYNames = List of names corresponding to rightYData (list of strings).
91  # If equal to None, no legend is created. Default None.
92  # @param leftYVisible = List of visibility flags corresponding to leftYData (list of bool).
93  # If equal to None, all curves are visible. Default None.
94  # @param rightYVisible = List of visibility flags corresponding to rightYData (list of bool).
95  # If equal to None, all curves are visible. Default None.
96  # @param XLabel = X-axis label (string). Default None.
97  # @param leftYLabel = Left y-axis label (string). Default None.
98  # @param rightYLabel = Right y-axis label (string). Default None.
99  # @param leftYColors = List of colors corresponding to leftYData (list of Qt.QColor instances, like Qt.red or Qt.blue).
100  # If equal to None, default colors are used. Default None.
101  # @param rightYColors = List of colors corresponding to rightYData (list of Qt.QColor instances, like Qt.red or Qt.blue).
102  # If equal to None, default colors are used. Default None.
103  # @param Xunits = X-axis units for markers (string). If equal to None, no units are displayed. Default None.
104  # @param leftYunits = Ledt Y-axis units for markers and tracker (string). If equal to None, no units are displayed. Default None.
105  # @param rightYunits = Right Y-axis units for markers and tracker (string). If equal to None, no units are displayed. Default None.
106  # @param textLabel = Text to display as a label (string). Can be multiline. Default None.
107  # @param parent = Parent widget, in this case is mainWindow. Default None.
108  #
109  def __init__(self, XData, leftYData, rightXData = None, rightYData = None, leftWidth = None, rightWidth = None, leftYmin = None, leftYmax = None, rightYmin = None, rightYmax = None, Xmin = None, Xmax = None, windowTitle = None, tabTitle = None, title=None, leftYNames=None, rightYNames=None, leftYVisible=None, rightYVisible=None, XLabel=None, leftYLabel=None, rightYLabel=None, leftYColors=None, rightYColors=None, textLabel=None, Xunits=None, leftYunits=None, rightYunits = None, parent=None):
110 
111  super(DbPlot, self).__init__(parent)
112  self.setupUi(self)
113  self.setAttribute(Qt.WA_DeleteOnClose)
114 
115  if windowTitle is not None: self.setWindowTitle(windowTitle)
116  self.statusBar.showMessage('Use toolbar buttons to zoom, add or delete markers')
117 
118  # Add what's this action
119  self.toolBar.addAction(QWhatsThis.createAction(self.toolBar))
120 
121  ##
122  # Move marker knob
123  self.moveKnob = None
124 
125 # # Knob version
126 # self.moveKnob = Qwt.QwtKnob(self)
127 # self.moveKnob.setKnobWidth(24)
128 # self.moveKnob.setTotalAngle(300)
129 # self.toolBar.insertWidget(self.actionDeleteMarker, self.moveKnob)
130 
131  # Slider version
132  self.moveKnob = Qwt.QwtSlider(self, Qt.Horizontal, Qwt.QwtSlider.BottomScale, Qwt.QwtSlider.BgSlot)
133  self.moveKnob.setScaleMaxMajor(24)
134  self.moveKnob.setScaleMaxMinor(5)
135  self.verticalLayout.addWidget(self.moveKnob)
136 
137  # Common to Knob and Slider versions
138  self.moveKnob.setVisible(False)
139  self.connect(self.moveKnob, SIGNAL('valueChanged(double)'), self.on_moveKnob_valueChanged )
140 
141  # Display text labels
142  if textLabel is not None:
143  # Multiline text in Qlabel does not work with html rich-text (why?).
144  # We have to split the text as a list of lines and create a Qlabel for each line.
145  textLabel = textLabel.split('\n')
146  self.label = []
147 
148  for st in textLabel:
149  newLabel = QLabel(st, self.centralWidget)
150  self.label.append (newLabel)
151  self.verticalLayout.addWidget(newLabel)
152 
153  ##
154  # Number of TabWidgets
155  self.Ntabs = 0
156 
157  ##
158  # List of QwtPlot class instances
159  self.hPlot = []
160 
161  ##
162  # Left axis zoomer. List for all Tabs.
163  self.zoomerLeft = []
164 
165  ##
166  # Right axis zoomer. List for all Tabs.
167  self.zoomerRight = []
168 
169  ##
170  # Left axis picker. List for all Tabs.
171  self.pickerLeft = []
172 
173  ##
174  # Axis scaling dialog. List for all Tabs.
175  self.scalingDlg = []
176 
177  ##
178  # Current instance of scalingDlg class, if the scalingDlg window is open, None if it is closed.
179  self.scalingDlgCurrent = None
180 
181  ##
182  # Dialog for setting curves visibility. List for all Tabs.
183  self.setCurves = []
184 
185  ##
186  # Current instance of EditCurvesDlg class, if the setCurves window is open, None if it is closed.
187  self.setCurvesCurrent = None
188 
189  ##
190  # List of curves in left y-axis. List for all Tabs.
191  self.leftYCurves = []
192 
193  ##
194  # List of curves in right y-axis. List for all Tabs.
195  self.rightYCurves = []
196 
197  ##
198  # List of specification masks. List for all Tabs.
199  self.masks = []
200 
201  ##
202  # List of markers. List for all Tabs.
203  self.markers = []
204 
205  ##
206  # List of XData. List for all Tabs.
207  self.XData = []
208 
209  ##
210  # List of leftYData. List for all Tabs.
211  self.leftYData = []
212 
213  ##
214  # List of rightXData. List for all Tabs.
215  self.rightXData = []
216 
217  ##
218  # List of rightYData. List for all Tabs.
219  self.rightYData = []
220 
221  ##
222  # List of left y-axis curve names. List for all Tabs.
223  self.leftYNames = []
224 
225  ##
226  # List of right y-axis curve names. List for all Tabs.
227  self.rightYNames = []
228 
229  ##
230  # List of mask names. List for all Tabs.
231  self.maskNames = []
232 
233  ##
234  # Legend for each plot. List for all Tabs.
235  self.legends = []
236 
237  ##
238  # Zoomer base for autoscaling. List for all Tabs.
239  self.autoScaleBase = []
240 
241  ##
242  # X-axis units for markers. List for all Tabs.
243  self.Xunits = []
244 
245  ##
246  # Left Y-axis units for markers. List for all Tabs.
247  self.leftYunits = []
248 
249  ##
250  # Right Y-axis units for markers. List for all Tabs.
251  self.rightYunits = []
252 
253  ##
254  # Marker to move
255  self.markerMove = None
256 
257  ##
258  # Size of curve symbols, when curve points are visible for adding new marker.
259  self.curveSymbolSize = None
260 
261  ##
262  # Number of x-axis significant digits required to display markers and tracker labels, depending on axis scaling. List for all Tabs.
263  self.precX = []
264 
265  ##
266  # Number of left- y axis significant digits required to display markers and tracker labels, depending on axis scaling. List for all Tabs.
267  self.precY = []
268 
269  ##
270  # Flag that is True when the program processes mouse dragging. If True, LeftButton clicks are discarded.
271  self.draggingMode = False
272 
273  # Create Tab
274  self.addTab(XData = XData, leftYData = leftYData, rightXData = rightXData, rightYData = rightYData, leftWidth = leftWidth, rightWidth = rightWidth, leftYmin = leftYmin, leftYmax = leftYmax, rightYmin = rightYmin, rightYmax = rightYmax, Xmin = Xmin, Xmax = Xmax , tabTitle = tabTitle, title = title, leftYNames = leftYNames, rightYNames = rightYNames, leftYVisible = leftYVisible, rightYVisible = rightYVisible, XLabel = XLabel, leftYLabel = leftYLabel, rightYLabel = rightYLabel, leftYColors = leftYColors, rightYColors = rightYColors, Xunits = Xunits, leftYunits = leftYunits, rightYunits = rightYunits)
275 
276  # Show plot
277  self.resize(800, 600)
278  self.show()
279 
280  ##
281  # Link to the mainWindow.
282  self.mainWindow = parent
283 
284  ##
285  # Flag that will be set to true when the window is closed, in order to allow reconstruction of the class
286  # instead of updating the plot when execute or compute buttons are clicked in other windows.
287  self.closed = False
288 
289 
290  ##
291  #
292  # Adds new tab with plot.
293  #
294  # @param XData = List of X-axis data (list of numpy arrays).
295  # Each element in the list is the x-axis data for the corresponding element in leftYData.
296  # When there is only one curve, it is not necessary to create a list of curves: XData argument can be the curve x-data alone.
297  # @param leftYData = List of curve-data to plot in left y-axis (list of numpy arrays).
298  # Curve-data can be a list of int or float or a numpy array.
299  # If a list element is a 2-D numpy array, it defines a curve family sharing the same curve properties.
300  # Curves in a curve family have Ydata in the columns of the 2-D numpy array.
301  # When there is only one curve, it is not necessary to create a list of curves: leftYData argument can be the curve-data alone.
302  # @param rightXData = List of X-axis data (list of numpy arrays).
303  # Each element in the list is the x-axis data for the corresponding element in rightYData.
304  # When there is only one curve, it is not necessary to create a list of curves: rightXData argument can be the curve x-data alone.
305  # If equal to None, XData is used both for left-y and right-y axis data.
306  # Default None.
307  # @param rightYData = List of curve-data to plot in right y-axis (list of numpy arrays).
308  # Curve-data can be a list of int or float or a numpy array.
309  # If a list element is a 2-D numpy array, it defines a curve family sharing the same curve properties.
310  # Curves in a curve family have Ydata in the columns of the 2-D numpy array.
311  # When there is only one curve, it is not necessary to create a list of curves: rightYData argument can be the curve-data alone.
312  # If it is equal to None, there is no data in the right-Y axis. Default None.
313  # @param leftYmin = Set manual scaling for left-y axis with this minimum value.
314  # @param leftYmax = Set manual scaling for left-y axis with this maximum value.
315  # @param rightYmin = Set manual scaling for right-y axis with this minimum value.
316  # @param rightYmax = Set manual scaling for right-y axis with this maximum value.
317  # @param Xmin = Set manual scaling for x axis with this minimum value.
318  # @param Xmax = Set manual scaling for x axis with this maximum value.
319  # @param tabTitle = Tab title (string). Default None.
320  # @param title = Plot title (string). Default None.
321  # @param leftYNames = List of names corresponding to leftYData (list of strings).
322  # If equal to None, no legend is created. Default None.
323  #
324  # @param rightYNames = List of names corresponding to rightYData (list of strings).
325  # If equal to None, no legend is created. Default None.
326  # @param leftYVisible = List of visibility flags corresponding to leftYData (list of bool).
327  # If equal to None, all curves are visible. Default None.
328  # @param rightYVisible = List of visibility flags corresponding to rightYData (list of bool).
329  # If equal to None, all curves are visible. Default None.
330  # @param XLabel = X-axis label (string). Default None.
331  # @param leftYLabel = Left y-axis label (string). Default None.
332  # @param rightYLabel = Right y-axis label (string). Default None.
333  # @param leftYColors = List of colors corresponding to leftYData (list of Qt.QColor instances, like Qt.red or Qt.blue).
334  # If equal to None, default colors are used. Default None.
335  # @param rightYColors = List of colors corresponding to rightYData (list of Qt.QColor instances, like Qt.red or Qt.blue).
336  # If equal to None, default colors are used. Default None.
337  # @param Xunits = X-axis units for markers (string). If equal to None, no units are displayed. Default None.
338  # @param leftYunits = Left Y-axis units for markers and tracker (string). If equal to None, no units are displayed. Default None.
339  # @param rightYunits = Right Y-axis units for markers and tracker (string). If equal to None, no units are displayed. Default None.
340  # @param replaceTab = If not None, it is the index of an already existing Tab and all the CurveFamilies have to be replaced with data from the function arguments. Default False.
341  #
342  def addTab(self, XData, leftYData, rightXData = None, rightYData = None, leftWidth = None, rightWidth = None, leftYmin = None, leftYmax = None, rightYmin = None, rightYmax = None, Xmin = None, Xmax = None, tabTitle = None, title=None, leftYNames=None, rightYNames=None, leftYVisible=None, rightYVisible=None, XLabel=None, leftYLabel=None, rightYLabel=None, leftYColors=None, rightYColors=None, Xunits=None, leftYunits=None, rightYunits=None, replaceTab=None):
343  if replaceTab is None:
344  self.Ntabs += 1
345  nTab = self.Ntabs-1 # Current Tab index in lists
346  tab = QWidget()
347  verticalLayout_tab = QVBoxLayout(tab)
348  self.tabWidget.addTab(tab, "Plot")
349 
350  # Add data
351  self.XData.append(XData)
352  self.leftYData.append(leftYData)
353  self.rightXData.append(rightXData)
354  self.rightYData.append(rightYData)
355 
356  # Add axis units
357  self.Xunits.append( ' ' + Xunits if Xunits is not None else '' )
358  self.leftYunits.append( ' ' + leftYunits if leftYunits is not None else '' )
359  self.rightYunits.append( ' ' + rightYunits if rightYunits is not None else '' )
360 
361  # Add layout for axis scaling controls
362  scalingDlg = AxisScalingDlg(self)
363  self.scalingDlg.append(scalingDlg)
364  scalingDlg.setWindowTitle('Axis scaling: %s' % tabTitle)
365 
366  # Make a QwtPlot widget
367  self.hPlot.append(Qwt.QwtPlot(tab))
368  verticalLayout_tab.addWidget(self.hPlot[nTab])
369  hPlot = self.hPlot[nTab]
370 
371  # Set grid
372  hPlot.plotLayout().setAlignCanvasToScales(True)
373  grid = Qwt.QwtPlotGrid()
374  grid.attach(hPlot)
375  grid.setPen(QPen(Qt.black, 0, Qt.DotLine))
376 
377  # Set canvas color
378  hPlot.setCanvasBackground(Qt.white)
379 
380  else: # Replace data in already existing Tab
381  nTab= replaceTab
382  self.XData[nTab] = XData
383  self.leftYData[nTab] = leftYData
384  self.rightXData[nTab] = rightXData
385  self.rightYData[nTab] = rightYData
386 
387  # Replace axis units
388  self.Xunits[nTab] = ' ' + Xunits if Xunits is not None else ''
389  self.leftYunits[nTab] = ' ' + leftYunits if leftYunits is not None else ''
390  self.rightYunits[nTab] = ' ' + rightYunits if rightYunits is not None else ''
391 
392  hPlot = self.hPlot[nTab]
393  scalingDlg = self.scalingDlg[nTab]
394 
395  # Set Tab abd Plot titles
396  if tabTitle is not None: self.tabWidget.setTabText(nTab, tabTitle)
397  if title is not None: hPlot.setTitle(title)
398  if rightYData is not None: hPlot.enableAxis(Qwt.QwtPlot.yRight)
399 
400  # Set axis titles
401  if XLabel is not None: hPlot.setAxisTitle(Qwt.QwtPlot.xBottom, XLabel)
402  if leftYLabel is not None: hPlot.setAxisTitle(Qwt.QwtPlot.yLeft, leftYLabel)
403  if rightYLabel is not None: hPlot.setAxisTitle(Qwt.QwtPlot.yRight, rightYLabel)
404 
405  # Set default colors
406  defaultColors = [Qt.blue, Qt.red, QColor(0, 175, 0), QColor(191, 0, 191), Qt.black, QColor(0, 191, 191), QColor(191, 191, 0) ]
407 
408  # When there is only one curve, make sure that x-data, y-data and Names are an one-element list.
409  if type(leftYData[0]).__name__ not in ['ndarray', 'list']: leftYData = [ leftYData ]
410  elif type(leftYData).__name__ == 'ndarray' and leftYData.ndim == 2: leftYData = [ leftYData ] # A single curve family containing a 2-D array
411 
412  if type(XData[0]).__name__ not in ['ndarray', 'list']: XData = [ XData ]
413 
414  if rightYData is not None:
415  if type(rightYData[0]).__name__ not in ['ndarray', 'list']: rightYData = [ rightYData ]
416  elif type(rightYData).__name__ == 'ndarray' and rightYData.ndim == 2: rightYData = [ rightYData ] # A single curve family containing a 2-D array
417 
418  if rightXData is not None:
419  if type(rightXData[0]).__name__ not in ['ndarray', 'list']: rightXData = [ rightXData ]
420 
421  if leftYNames is not None:
422  if type(leftYNames).__name__ not in ['ndarray', 'list']: leftYNames = [ leftYNames ]
423  self.leftYNames.append(leftYNames)
424  if rightYNames is not None:
425  if type(rightYNames).__name__ not in ['ndarray', 'list']: rightYNames = [ rightYNames ]
426  self.rightYNames.append(rightYNames)
427  if leftYVisible is not None:
428  if type(leftYVisible).__name__ not in ['ndarray', 'list']: leftYVisible = [ leftYVisible]
429  if rightYVisible is not None:
430  if type(rightYVisible).__name__ not in ['ndarray', 'list']: rightYVisible = [ rightYVisible ]
431  if leftYColors is not None:
432  if type(leftYColors).__name__ not in ['ndarray', 'list']: leftYColors = [ leftYColors ]
433  if rightYColors is not None:
434  if type(rightYColors).__name__ not in ['ndarray', 'list']: rightYColors = [ rightYColors ]
435  if leftWidth is not None:
436  if type(leftWidth).__name__ not in ['ndarray', 'list']: leftWidth = [ leftWidth ]
437  if rightWidth is not None:
438  if type(rightWidth).__name__ not in ['ndarray', 'list']: rightWidth = [ rightWidth ]
439 
440 
441  # in ['float', 'float64', 'int', 'complex']:
442 
443 
444  # Delete old curve families. Must be called before CF = CurveFamily()
445  if replaceTab is not None:
446  for CF in self.leftYCurves[nTab]: CF.deleteCurves()
447  for CF in self.rightYCurves[nTab]: CF.deleteCurves()
448 
449 
450  # Prepare visibility lists
451  if leftYVisible is None: leftYVisible = [ True for n in leftYData ] # By default, all curves are visible
452  else: assert len(leftYVisible) == len(leftYData)
453 
454  if rightYData is not None:
455  if rightYVisible is None: rightYVisible = [ True for n in rightYData ] # By default, all curves are visible
456  else: assert len(rightYVisible) == len(rightYData)
457 
458  # Set left-Y data
459  leftYCurves = [] # List of curves in left y-axis
460  if leftYNames is not None: assert len(leftYData) == len(leftYNames)
461  if leftYColors is not None: assert len(leftYData) == len(leftYColors)
462  if leftWidth is not None: assert len(leftYData) == len(leftWidth)
463 
464  # Create all the left y-axis curves
465  for n in range(0, len(leftYData)):
466  # Check if x-axis data is the same for all curves
467  xdata = XData[0] if len(XData) == 1 else XData[n]
468 
469  assert len(xdata) == len(leftYData[n]) # If leftYData[n] is a 2-D array, len() returns the number of rows
470  name = leftYNames[n] if leftYNames is not None else 'curve #%d' % n
471  color = leftYColors[n] if leftYColors is not None else defaultColors[n % len(defaultColors)]
472  width = leftWidth[n] if leftWidth is not None else None
473 
474  CF = CurveFamily(hPlot, name, xdata, leftYData[n], Qwt.QwtPlot.yLeft, leftYVisible[n], color, width)
475  leftYCurves.append(CF)
476 
477  # Set right-Y data
478  rightYCurves = [] # List of curves in right y-axis
479  if rightYData is not None:
480  if rightYNames is not None: assert len(rightYData) == len(rightYNames)
481  if rightYColors is not None: assert len(rightYData) == len(rightYColors)
482  if rightWidth is not None: assert len(rightYData) == len(rightWidth)
483 
484  # Create all the right y-axis curves
485  for n in range(0, len(rightYData)):
486  # Check if right-y curves use rightXData or XData
487  tmp = XData if rightXData is None else rightXData
488 
489  # Check if x-axis data is the same for all curves
490  xdata = tmp[0] if len(tmp) == 1 else tmp[n]
491 
492  assert len(xdata ) == len(rightYData[n])
493  name = rightYNames[n] if rightYNames is not None else 'curve #%d' % n
494  color = rightYColors[n] if rightYColors is not None else defaultColors[(n + len(leftYData)) % len(defaultColors)]
495  width = rightWidth[n] if rightWidth is not None else None
496 
497  CF = CurveFamily(hPlot, name, xdata, rightYData[n], Qwt.QwtPlot.yRight, rightYVisible[n], color, width)
498  rightYCurves.append(CF)
499 
500  if replaceTab is None:
501  # Install EventFilter in plot canvas.
502  # The filter does not discard processed events, so it can be installed before or after creating a QwtPlotPicker without preventing the events from being sent to the QwtPlotPicker.
503  # The filter that is installed last will process events first,
504  hPlot.canvas().installEventFilter(CanvasEventFilter(self, hPlot))
505 
506  # Set zoomer and tracker
507  zoomerLeft = Qwt.QwtPlotZoomer(Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.DragSelection, Qwt.QwtPicker.AlwaysOff, hPlot.canvas())
508  self.zoomerLeft.append(zoomerLeft)
509  self.zoomerLeft[nTab].setRubberBandPen(QPen(Qt.green))
510  self.zoomerLeft[nTab].setEnabled(False)
511  self.connect(zoomerLeft, SIGNAL('zoomed(QwtDoubleRect)'), lambda value: self.getPrecision(nTab))
512  self.connect(zoomerLeft, SIGNAL('zoomed(QwtDoubleRect)'), lambda value: self.updateMoveKnobRange())
513 
514  zoomerRight = Qwt.QwtPlotZoomer(Qwt.QwtPlot.xTop, Qwt.QwtPlot.yRight, Qwt.QwtPicker.PointSelection | Qwt.QwtPicker.DragSelection, Qwt.QwtPicker.AlwaysOff, hPlot.canvas())
515  self.zoomerRight.append(zoomerRight)
516  self.zoomerRight[nTab].setRubberBand(Qwt.QwtPicker.NoRubberBand)
517  self.zoomerRight[nTab].setEnabled(False)
518 
519  # Set picker selection type to Qwt.QwtPicker.PointSelection | Qwt.QwtPicker.ClickSelection to allow marker selection
520  pickerLeft = MyPicker( self, nTab, Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.PointSelection | Qwt.QwtPicker.ClickSelection, Qwt.QwtPlotPicker.CrossRubberBand, Qwt.QwtPicker.AlwaysOn, hPlot.canvas())
521  pickerLeft.setTrackerPen(QPen(Qt.black))
522  self.pickerLeft.append(pickerLeft)
523 
524  # Dialog for setting curves visibility
525  self.setCurves.append(EditCurvesDlg(leftYCurves, rightYCurves, self))
526  else:
527  zoomerLeft = self.zoomerLeft[nTab]
528  zoomerRight = self.zoomerRight[nTab]
529  pickerLeft = self.pickerLeft[nTab]
530 
531  tmp = self.setCurves[nTab]
532  del tmp # Cannot delete directly del self.setCurves[nTab] because it would remove the list element also
533  self.setCurves[nTab] = EditCurvesDlg(leftYCurves, rightYCurves, self)
534 
535  if replaceTab is None: # Add lists of curves, masks and markers
536  self.leftYCurves.append(leftYCurves)
537  self.rightYCurves.append(rightYCurves)
538 
539  # Append empty masks list
540  self.masks.append( [] )
541  self.maskNames.append( [] )
542 
543  # Append empty markers list
544  self.markers.append( [] )
545 
546  # Append element to precision lists
547  self.precX.append(None)
548  self.precY.append(None)
549 
550  else:
551  # Replace lists of curves
552  self.leftYCurves[nTab] = leftYCurves
553  self.rightYCurves[nTab] = rightYCurves
554 
555  # Detach masks to autoscale without the masks data, and save visibility data
556  maskVisibility = []
557  for curve in self.masks[nTab]:
558  maskVisibility.append(curve.isVisible())
559  curve.detach()
560 
561  # Autoscale axis and get autoscale min and max for new data, based on Qwt autoscale
562  self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yLeft)
563  self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yRight)
564  self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.xBottom)
565  self.hPlot[nTab].replot()
566  dataLeftYmax, dataLeftYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yLeft)
567  dataRightYmax, dataRightYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yRight)
568  dataXmax, dataXmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.xBottom)
569 
570  # Attach masks
571  for n, curve in enumerate(self.masks[nTab]):
572  curve.attach(self.hPlot[nTab])
573  curve.setVisible(maskVisibility[n])
574  legendItem = self.legends[nTab].find(curve)
575  legendItem.setChecked(maskVisibility[n])
576 
577  # Update zoomer base with data from new curves
578  leftZoomerBaseRect, rightZoomerBaseRect = self.autoScaleBase[nTab].updateZoomerBase(dataXmin, dataXmax, dataLeftYmin, dataLeftYmax, dataRightYmin, dataRightYmax)
579  if leftZoomerBaseRect is not None: self.zoomerLeft[nTab].setZoomBase( leftZoomerBaseRect )
580  if rightZoomerBaseRect is not None: self.zoomerRight[nTab].setZoomBase( rightZoomerBaseRect )
581 
582  # Disable all marker, zoom and curve edit facilities
583  self.on_actionZoom_toggled(False)
584  self.on_actionNewMarker_toggled(False)
586  self.on_actionMoveMarker_toggled(False)
587  self.actionCurves.setChecked(False)
588 
589  # Delete markers (they were attached to old curve points)
590  if len(self.markers[nTab]) > 0:
591  for marker in self.markers[nTab]:
592  marker.detach()
593  del marker
594  self.markers[nTab] = []
595 
596  # Replace element in precision lists
597  self.precX[nTab] = None
598  self.precY[nTab] = None
599 
600 
601  # Initialise axis scaling controls
602  for (axis, groupBox, min_Counter, max_Counter) in zip( [ Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom ],
603  [ self.scalingDlg[nTab].leftY_groupBox, self.scalingDlg[nTab].rightY_groupBox, self.scalingDlg[nTab].X_groupBox ],
604  [ self.scalingDlg[nTab].leftYmin_Counter, self.scalingDlg[nTab].rightYmin_Counter, self.scalingDlg[nTab].Xmin_Counter ],
605  [ self.scalingDlg[nTab].leftYmax_Counter, self.scalingDlg[nTab].rightYmax_Counter, self.scalingDlg[nTab].Xmax_Counter ] ):
606 
607  maxScale, minScale = self.getScaleMaxMin(nTab, axis)
608  minorTicks = self.hPlot[nTab].axisScaleDiv(axis).ticks(Qwt.QwtScaleDiv.MinorTick)
609  mediumTicks = self.hPlot[nTab].axisScaleDiv(axis).ticks(Qwt.QwtScaleDiv.MediumTick)
610  majorTicks = self.hPlot[nTab].axisScaleDiv(axis).ticks(Qwt.QwtScaleDiv.MajorTick)
611 
612  maxminScale = maxScale - minScale
613  majorStep = majorTicks[1] - majorTicks[0]
614  mediumStep = mediumTicks[0] - majorTicks[0] if len(mediumTicks) > 0 else majorStep # The 1st tick is always majorTicks[0]
615  minorStep = minorTicks[0] - majorTicks[0] if len(minorTicks) > 0 else mediumStep # The 1st tick is always majorTicks[0]
616  NminorSteps = int(round(majorStep/minorStep))
617 
618 # print 'nTab:', nTab, 'axis:', axis, 'minorStep =', minorStep, 'mediumStep =', mediumStep, 'majorStep =', majorStep, 'NminorSteps =', NminorSteps
619 # print 'minor', [ '%g' % tmp for tmp in minorTicks ]
620 # print 'medium', [ '%g' % tmp for tmp in mediumTicks ]
621 # print 'major', [ '%g' % tmp for tmp in majorTicks ]
622 
623  min_Counter.setStepButton1(1)
624  min_Counter.setStepButton2(NminorSteps)
625  max_Counter.setStepButton1(1)
626  max_Counter.setStepButton2(NminorSteps)
627 
628  max_Counter.setRange(minScale, maxScale, minorStep)
629  max_Counter.setValue(maxScale)
630  min_Counter.setRange(minScale, maxScale, minorStep)
631  min_Counter.setValue(minScale)
632 
633  groupBox.setToolTip('Double arrow increments/decrements %g, simple arrow %g' % (minorStep*NminorSteps, minorStep) )
634  groupBox.setChecked(False) # Start with manual scaling disabled
635 
636 
637  if replaceTab is None:
638  # Connect widgets to slots. For some reason, it does not work inside the for loop above
639  self.connect(self.scalingDlg[nTab].leftYmax_Counter, SIGNAL('valueChanged(double)'), lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.yLeft) )
640  self.connect(self.scalingDlg[nTab].leftYmin_Counter, SIGNAL('valueChanged(double)'), lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.yLeft) )
641  self.connect(self.scalingDlg[nTab].leftY_groupBox, SIGNAL('toggled(bool)'), lambda value: self.setManualScaling(value, Qwt.QwtPlot.yLeft) )
642  self.connect(self.scalingDlg[nTab].autoLeftY_pushButton, SIGNAL('pressed()'), lambda : self.setAutoScaling(Qwt.QwtPlot.yLeft, self.scalingDlg[nTab].leftY_groupBox) )
643 
644  self.connect(self.scalingDlg[nTab].rightYmax_Counter, SIGNAL('valueChanged(double)'), lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.yRight) )
645  self.connect(self.scalingDlg[nTab].rightYmin_Counter, SIGNAL('valueChanged(double)'), lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.yRight) )
646  self.connect(self.scalingDlg[nTab].rightY_groupBox, SIGNAL('toggled(bool)'), lambda value: self.setManualScaling(value, Qwt.QwtPlot.yRight) )
647  self.connect(self.scalingDlg[nTab].autoRightY_pushButton, SIGNAL('pressed()'), lambda : self.setAutoScaling(Qwt.QwtPlot.yRight, self.scalingDlg[nTab].rightY_groupBox) )
648 
649  self.connect(self.scalingDlg[nTab].Xmax_Counter, SIGNAL('valueChanged(double)'), lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.xBottom) )
650  self.connect(self.scalingDlg[nTab].Xmin_Counter, SIGNAL('valueChanged(double)'), lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.xBottom) )
651  self.connect(self.scalingDlg[nTab].X_groupBox, SIGNAL('toggled(bool)'), lambda value: self.setManualScaling(value, Qwt.QwtPlot.xBottom) )
652  self.connect(self.scalingDlg[nTab].autoX_pushButton, SIGNAL('pressed()'), lambda : self.setAutoScaling(Qwt.QwtPlot.xBottom, self.scalingDlg[nTab].X_groupBox) )
653 
654  # Check is there is a manual scaling argument and update Counter. Set scale to counter values.
655  # If there are no manual scaling arguments, the counter values should be equal to the autoscaling zoom base, resulting in auto scale.
656  # This should work with replaceTab equal to None or to a replace Tab. In the later case, this is necessary to set scaling overriding the mask limits.
657  if leftYmin: self.scalingDlg[nTab].leftYmin_Counter.setValue(leftYmin)
658  if leftYmax: self.scalingDlg[nTab].leftYmax_Counter.setValue(leftYmax)
659  if leftYmin or leftYmax: self.scalingDlg[nTab].leftY_groupBox.setChecked(True)
660  else: self.scalingDlg[nTab].leftY_groupBox.setChecked(False)
661  self.hPlot[nTab].setAxisScale(Qwt.QwtPlot.yLeft, self.scalingDlg[nTab].leftYmin_Counter.value(), self.scalingDlg[nTab].leftYmax_Counter.value())
662 
663  if rightYmin: self.scalingDlg[nTab].rightYmin_Counter.setValue(rightYmin)
664  if rightYmax: self.scalingDlg[nTab].rightYmax_Counter.setValue(rightYmax)
665  if rightYmin or rightYmax: self.scalingDlg[nTab].rightY_groupBox.setChecked(True)
666  else: self.scalingDlg[nTab].rightY_groupBox.setChecked(False)
667  self.hPlot[nTab].setAxisScale(Qwt.QwtPlot.yRight, self.scalingDlg[nTab].rightYmin_Counter.value(), self.scalingDlg[nTab].rightYmax_Counter.value() )
668 
669  if Xmin: self.scalingDlg[nTab].Xmin_Counter.setValue(Xmin)
670  if Xmax: self.scalingDlg[nTab].Xmax_Counter.setValue(Xmax)
671  if Xmin or Xmax: self.scalingDlg[nTab].X_groupBox.setChecked(True)
672  else: self.scalingDlg[nTab].X_groupBox.setChecked(False)
673  self.hPlot[nTab].setAxisScale(Qwt.QwtPlot.xBottom, self.scalingDlg[nTab].Xmin_Counter.value(), self.scalingDlg[nTab].Xmax_Counter.value() )
674 
675  if replaceTab is None:
676  # Create legend
677  legend = Qwt.QwtLegend()
678  legend.setItemMode(Qwt.QwtLegend.CheckableItem)
679 
680  hPlot.insertLegend(legend, Qwt.QwtPlot.RightLegend)
681  self.legends.append(legend)
682  self.connect(self.hPlot[nTab], SIGNAL('legendChecked(QwtPlotItem*, bool)'), self.showCurve)
683  updateLegend = False
684  else:
685  legend = self.legends[nTab]
686  updateLegend = True # Trying to update legend size after replacing Tab data, but it does nothing
687 
688  # Legend layout
689  legendLayout = legend.contentsWidget().children()[0]
690 
691  # Set legend item checked / unchecked
692  for family in leftYCurves + rightYCurves: family.setLegendItems(legend=legend, updateLegend=updateLegend)
693 
694  # Finally, plot with the data
695  self.getPrecision(nTab)
696  hPlot.replot()
697 
698  if replaceTab is None:
699  # Store zoomer base, for later autoscaling. If replaceTab is not None, it has already been updated.
700  self.autoScaleBase.append(AutoScaleZoomerBase(self.zoomerLeft[nTab].zoomBase(), self.zoomerRight[nTab].zoomBase()))
701 
702  if replaceTab is None:
703  # Set first tab active. It updates the markerMove knob since self.updateMoveKnobRange() is called by self.on_tabWidget_currentChanged().
705  else:
706  # Update markerMove Knob limits, without changing the curent Tab
707  self.updateMoveKnobRange()
708 
709 
710  ##
711  #
712  # Add specification mask.
713  # In order to split the mask into separated parts, use numpy NaN in y-axis (numpy.nan), as show in the example at the end of this file.
714  # @param nTab = TabWidget page index.
715  # @param XData = x data (float numpy array).
716  # @param YData = y data (float numpy array).
717  # @param axis = 'left' or 'right' (string). Y-axis to which the YData values correspond.
718  # @param name = String to display in legend
719  # @param color = Qt.QColor instance, like Qt.red or Qt.blue
720  # @param type = 'upper' or 'lower' (string).
721  # @param visible = Visibility flag. If True the mask curve is initially visible, otherwise it is hidden. Default True.
722  # @param transpar = Alpha value for transparency (255=opaque, 0=transparent). Default 64.
723  #
724  def addMask(self, nTab, XData, YData, axis, name, color, type, visible = True, transpar = 64):
725 
726  # Get current axis scale, in order to restore it later
727  yLeftScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.yLeft)
728  yRightScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.yRight)
729  xBottomScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.xBottom)
730  leftYmin, leftYmax, rightYmin, rightYmax, Xmin, Xmax = yLeftScale.lowerBound(), yLeftScale.upperBound(), yRightScale.lowerBound(), yRightScale.upperBound(), xBottomScale.lowerBound(), xBottomScale.upperBound()
731 
732  if type == 'Lower': baseline = -1e7 # 1e8 or lager does not work
733  elif type == 'Upper': baseline = 1e7 # 1e8 or lager does not work
734 
735  assert type.lower() in ['upper', 'lower']
736  assert axis.lower() in ['left', 'right']
737  curve = Qwt.QwtPlotCurve(name)
738 # curve.setRenderHint(Qwt.QwtPlotItem.RenderAntialiased) # This was in an example
739  semitrans = QColor(color)
740  semitrans.setAlpha(transpar)
741  curve.setPen(QPen(semitrans))
742  curve.setBrush(semitrans)
743  curve.setVisible(visible)
744  curve.setBaseline( baseline)
745  curve.attach(self.hPlot[nTab])
746  if axis.lower() == 'left': curve.setYAxis(Qwt.QwtPlot.yLeft)
747  elif axis.lower() == 'right': curve.setYAxis(Qwt.QwtPlot.yRight)
748 
749  # Set mask limits at baseline
750  XData = np.concatenate(( np.array([ XData[0]] ), XData, np.array([ XData[-1]]) ))
751  YData = np.concatenate(( np.array([ baseline ]), YData, np.array([ baseline ]) ))
752 
753  curve.setData(XData, YData)
754  legendItem = self.legends[nTab].find(curve)
755  legendItem.setChecked(visible)
756  idenWidth = legendItem.identifierWidth()
757  legendItem.setIdentifierMode(Qwt.QwtLegendItem.ShowSymbol)
758  symbol = Qwt.QwtSymbol( Qwt.QwtSymbol.Rect, semitrans, QPen(semitrans), QSize(2*idenWidth, 2*idenWidth) )
759  legendItem.setSymbol(symbol)
760 
761  self.masks[nTab].append(curve)
762  self.maskNames[nTab].append(name)
763 
764  # Restore axis scaling to previous manual mode
765  self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yLeft, leftYmin, leftYmax )
766  self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yRight, rightYmin, rightYmax )
767  self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.xBottom, Xmin, Xmax )
768 
769  self.hPlot[nTab].replot()
770 
771 
772  ##
773  #
774  # Delete all masks.
775  # Does nothing if there are no masks.
776  #
777  def deleteMasks(self):
778 
779  for nTab in range(self.Ntabs):
780  for curve in self.masks[nTab]: curve.detach()
781  self.masks[nTab] = []
782  self.maskNames[nTab] = []
783  self.hPlot[nTab].replot()
784 
785 
786 
787  ##
788  #
789  # Update an existing plot with new data.
790  # The number of curves in the leftY and rightY axis must be the same as in the existing plot before update.
791  # A change in the number of curves will result in an assertion error.
792  # @param nTab = TabWidget page index.
793  # @param XData = x-axis data (float numpy array or list of floats).
794  # @param leftYData = List of arrays to plot in left y-axis (list of numpy arrays or list of lists of floats).
795  # @param rightXData = x-axis data for right-axis curves (float numpy array or list of floats).
796  # If equal to None, XData is used both for left-y and right-y axis data. Default None.
797  # @param rightYData = List of arrays to plot in right y-axis (list of numpy arrays or list of lists of floats). Default None.
798  # @param textLabel = Text to display as a label (string). Can be multiline. Default None.
799  #
800  def update(self, nTab, XData, leftYData, rightXData = None, rightYData=None, textLabel=None, autoScaleBottomX=False, autoScaleLeftY=False, autoScaleRightY=False):
801 
802  # Get current axis scale, in order to restore it later
803  yLeftScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.yLeft)
804  yRightScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.yRight)
805  xBottomScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.xBottom)
806  leftYmax, leftYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yLeft)
807  rightYmax, rightYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yRight)
808  Xmax, Xmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.xBottom)
809 
810  # Update label
811  if textLabel is not None:
812  textLabel = textLabel.split('\n')
813  assert len(textLabel) == len(self.label)
814  for n in range(len(textLabel)): self.label[n].setText(textLabel[n])
815 
816  # Check if there is only one curve and it is not inside a curve list
817  if type(XData[0]).__name__ not in ['ndarray', 'list']: XData = [ XData ]
818 
819  if type(leftYData[0]).__name__ not in ['ndarray', 'list']: leftYData = [ leftYData ]
820  elif type(leftYData).__name__ == 'ndarray' and leftYData.ndim == 2: leftYData = [ leftYData ] # A single curve family containing a 2-D array
821 
822  if rightXData is not None:
823  if type(rightXData[0]).__name__ not in ['ndarray', 'list']: rightXData = [ rightXData ]
824 
825  if rightYData is not None:
826  if type(rightYData[0]).__name__ not in ['ndarray', 'list']: rightYData = [ rightYData ]
827  elif type(rightYData).__name__ == 'ndarray' and rightYData.ndim == 2: rightYData = [ rightYData ] # A single curve family containing a 2-D array
828 
829  dataXmin, dataXmax, dataLeftYmin, dataLeftYmax, dataRightYmin, dataRightYmax = np.inf, -np.inf, np.inf, -np.inf, np.inf, -np.inf
830 
831  assert len(self.leftYCurves[nTab]) == len(leftYData)
832  for n in range(len(self.leftYCurves[nTab])):
833  # Check if x-axis data is the same for all curves
834  xdata = XData[0] if len(XData) == 1 else XData[n]
835 
836  # Update curve family and get data min and max
837  CF = self.leftYCurves[nTab][n]
838  cfXmin, cfXmax, cfYmin, cfYmax = CF.updateCurves(xdata, leftYData[n])
839 
840 # # Set axis max and min, based on curve data
841 # if cfXmin < dataXmin: dataXmin = cfXmin
842 # if cfXmax > dataXmax: dataXmax = cfXmax
843 # if cfYmin < dataLeftYmin: dataLeftYmin = cfYmin
844 # if cfYmax > dataLeftYmax: dataLeftYmax = cfYmax
845 
846  if rightYData is not None:
847  assert len(self.rightYCurves[nTab]) == len(rightYData)
848  for n in range(len(self.rightYCurves[nTab])):
849  # Check if right-y curves use rightXData or XData
850  tmp = XData if rightXData is None else rightXData
851 
852  # Check if x-axis data is the same for all curves
853  xdata = tmp[0] if len(tmp) == 1 else tmp[n]
854 
855  # Update curve family and get data min and max
856  CF = self.rightYCurves[nTab][n]
857  cfXmin, cfXmax, cfYmin, cfYmax = CF.updateCurves(xdata, rightYData[n])
858 
859 # # Set axis max and min, based on curve data
860 # if cfXmin < dataXmin: dataXmin = cfXmin
861 # if cfXmax > dataXmax: dataXmax = cfXmax
862 # if cfYmin < dataRightYmin: dataRightYmin = cfYmin
863 # if cfYmax > dataRightYmax: dataRightYmax = cfYmax
864 
865  # Detach masks to autoscale without the masks data, and save visibility data
866  maskVisibility = []
867  for curve in self.masks[nTab]:
868  maskVisibility.append(curve.isVisible())
869  curve.detach()
870 
871  # Get autoscale min and max for new data, based on Qwt autoscale
872  self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yLeft)
873  self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yRight)
874  self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.xBottom)
875  self.hPlot[nTab].replot()
876  dataLeftYmax, dataLeftYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yLeft)
877  dataRightYmax, dataRightYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yRight)
878  dataXmax, dataXmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.xBottom)
879 
880  # Attach masks
881  for n, curve in enumerate(self.masks[nTab]):
882  curve.attach(self.hPlot[nTab])
883  curve.setVisible(maskVisibility[n])
884  legendItem = self.legends[nTab].find(curve)
885  legendItem.setChecked(maskVisibility[n])
886 
887  # Update zoomer base with data from new curves
888  leftZoomerBaseRect, rightZoomerBaseRect = self.autoScaleBase[nTab].updateZoomerBase(dataXmin, dataXmax, dataLeftYmin, dataLeftYmax, dataRightYmin, dataRightYmax)
889  if leftZoomerBaseRect is not None: self.zoomerLeft[nTab].setZoomBase( leftZoomerBaseRect )
890  if rightZoomerBaseRect is not None: self.zoomerRight[nTab].setZoomBase( rightZoomerBaseRect )
891 
892  # Update manual scaling counters with data from new curves
893  self.scalingDlg[nTab].Xmin_Counter.setRange(dataXmin, dataXmax, self.scalingDlg[nTab].Xmin_Counter.step())
894  self.scalingDlg[nTab].Xmax_Counter.setRange(dataXmin, dataXmax, self.scalingDlg[nTab].Xmax_Counter.step())
895  if self.scalingDlg[nTab].Xmin_Counter.value() == dataXmax: self.scalingDlg[nTab].Xmin_Counter.setValue(dataXmin)
896  if self.scalingDlg[nTab].Xmax_Counter.value() == dataXmin:self.scalingDlg[nTab].Xmax_Counter.setValue(dataXmax)
897 
898  self.scalingDlg[nTab].leftYmin_Counter.setRange(dataLeftYmin, dataLeftYmax, self.scalingDlg[nTab].leftYmin_Counter.step())
899  self.scalingDlg[nTab].leftYmax_Counter.setRange(dataLeftYmin, dataLeftYmax, self.scalingDlg[nTab].leftYmax_Counter.step())
900  if self.scalingDlg[nTab].leftYmin_Counter.value() == dataLeftYmax: self.scalingDlg[nTab].leftYmin_Counter.setValue(dataLeftYmin)
901  if self.scalingDlg[nTab].leftYmax_Counter.value() == dataLeftYmin: self.scalingDlg[nTab].leftYmax_Counter.setValue(dataLeftYmax)
902 
903  self.scalingDlg[nTab].rightYmin_Counter.setRange(dataRightYmin, dataRightYmax, self.scalingDlg[nTab].rightYmin_Counter.step())
904  self.scalingDlg[nTab].rightYmax_Counter.setRange(dataRightYmin, dataRightYmax, self.scalingDlg[nTab].rightYmax_Counter.step())
905  if self.scalingDlg[nTab].rightYmin_Counter.value() == dataRightYmax: self.scalingDlg[nTab].rightYmin_Counter.setValue(dataRightYmin)
906  if self.scalingDlg[nTab].rightYmax_Counter.value() == dataRightYmin: self.scalingDlg[nTab].rightYmax_Counter.setValue(dataRightYmax)
907 
908  # If autoscale, uncheck manual axis scaling, otherwise restore axis scaling to previous
909  if autoScaleLeftY:
910  self.scalingDlg[nTab].leftY_groupBox.setChecked(False)
911  else:
912  self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yLeft, leftYmin, leftYmax )
913 
914  if autoScaleRightY:
915  self.scalingDlg[nTab].rightY_groupBox.setChecked(False)
916  else:
917  self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yRight, rightYmin, rightYmax )
918 
919  if autoScaleBottomX:
920  self.scalingDlg[nTab].X_groupBox.setChecked(False)
921  else:
922  self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.xBottom, Xmin, Xmax )
923 
924  # Move markers to new points position
925  for marker in self.markers[nTab]: marker.updatePosition()
926 
927  self.hPlot[nTab].replot()
928 
929 
930  ##
931  #
932  # Deletes the last marker appended to current tab.
933  #
934  def deleteLastMarker(self):
935  if not self.actionNewMarker.isChecked(): return # Only delete last marker when New Marker action is checked
936 
937  nTab = self.tabWidget.currentIndex()
938 
939  if len(self.markers[nTab]) == 0:
940  QMessageBox.warning(self, "WARNING", 'There are no markers to delete in this Tab.')
941  return
942 
943  markerDel = self.markers[nTab][-1]
944 
945  message = 'Delete marker: %s' % markerDel.label().text()
946  reply = QMessageBox.question(self, "%s - Delete Marker" % applicationName, message, QMessageBox.Yes | QMessageBox.No)
947 
948  if reply == QMessageBox.Yes:
949  markerDel.detach()
950  self.hPlot[nTab].replot()
951  self.markers[nTab].remove(markerDel)
952  del markerDel
953 
954 
955  ##
956  #
957  # Returns marker closer to the mouse cursor in screen coordinates, if distance is smaller than marker radius.
958  # @param nTab = Tab index
959  # @param point = Point coordinates (QPoint)
960  # @return markerSelected = QwtPlotMarker instance if marker is selected, None otherwise.
961  #
962  def selectMarker(self, nTab, point):
963 
964  if len(self.markers[nTab]) == 0:
965  QMessageBox.warning(self, "WARNING", 'There are no markers in this Tab.')
966  return
967 
968  hPlot = self.hPlot[nTab]
969 
970  scX, scY = point.x(), point.y()
971 
972 # # This is necessary only when point is in graph coordinates and must be transformed to canvas pixel coordinates.
973 # scX = hPlot.transform(Qwt.QwtPlot.xBottom, point.x())
974 # scY = hPlot.transform(Qwt.QwtPlot.yLeft, point.y())
975 
976  axis = None
977 
978  minDist2 = np.inf # Minimum distance squared
979  maxLeftY, minLeftY = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yLeft)
980  maxRightY, minRightY = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yRight)
981 
982  for marker in self.markers[nTab]:
983  if not marker.curve.isVisible(): continue # Do not select markers off non-visible curves
984 
985  if marker.axis == Qwt.QwtPlot.yLeft:
986  if marker.y > maxLeftY or marker.y < minLeftY: continue # Do not delete markers in non-visible points
987  distY = hPlot.transform(Qwt.QwtPlot.yLeft, marker.y) - scY # Distance in screen pixels
988 
989  elif marker.axis == Qwt.QwtPlot.yRight:
990  if marker.y > maxRightY or marker.y < minRightY: continue # Do not delete markers in non-visible points
991  distY = hPlot.transform(Qwt.QwtPlot.yRight, marker.y) - scY # Distance in screen pixels
992 
993  distX = hPlot.transform(Qwt.QwtPlot.xBottom, marker.x) - scX # Distance in screen pixels
994  dist2 = distX**2 + distY**2
995  if dist2 < minDist2:
996  minDist2 = dist2 # Distance to closer marker squared
997  markerSelected = marker
998  xDel = marker.x
999  yDel = marker.y
1000 
1001  markerRadius2 = (markerSelected.symbol().size().height()**2 + markerSelected.symbol().size().width()**2) / 4 # Marker radius squared
1002  if minDist2 <= markerRadius2:
1003  return markerSelected
1004  else:
1005  QMessageBox.warning(self, "WARNING", "No marker selected")
1006  return None
1007 
1008 
1009  ##
1010  #
1011  # Deletes marker if a marker is selected.
1012  # @param point = Point coordinates (QPoint)
1013  #
1014  def deleteMarker(self, point):
1015  if not self.actionDeleteMarker.isChecked(): return # Only delete markers when Delete Marker action is checked
1016 
1017  nTab = self.tabWidget.currentIndex()
1018  hPlot = self.hPlot[nTab]
1019 
1020  # Test if a marker is selected
1021  markerDel = self.selectMarker(nTab, point)
1022 
1023  if markerDel is not None:
1024  markerDel.setSymbol(markerDel.symbolDel)
1025  hPlot.replot()
1026  else:
1027  return
1028 
1029  message = 'Delete marker: %s' % markerDel.label().text()
1030  reply = QMessageBox.question(self, "%s - Delete Marker" % applicationName, message, QMessageBox.Yes | QMessageBox.No)
1031 
1032  if reply == QMessageBox.Yes:
1033  markerDel.detach()
1034  self.markers[nTab].remove(markerDel)
1035  del markerDel
1036  else:
1037  markerDel.setSymbol(markerDel.symbolNormal)
1038 
1039  hPlot.replot()
1040 
1041 
1042  ##
1043  #
1044  # Selects a marker, highlights it and enables marker movement.
1045  # @param point = Point coordinates (QPoint)
1046  #
1047  def selectAndHighlightMarker(self, point):
1048  if not self.actionMoveMarker.isChecked(): return # Only move markers when Move Marker action is checked
1049 
1050  nTab = self.tabWidget.currentIndex()
1051  markerMove = self.selectMarker(nTab, point)
1052  self.hightlightMarker(nTab, markerMove)
1053 
1054 
1055  ##
1056  #
1057  # Highlights marker and enables marker movement.
1058  # @param nTab = Tab index
1059  # @param markerMove = Marker to move (PlotMarker)
1060  #
1061  def hightlightMarker(self, nTab, markerMove):
1062  hPlot = self.hPlot[nTab]
1063 
1064  if markerMove is not None:
1065  markerMove.setSymbol(markerMove.symbolMove)
1066  if self.markerMove is not None: self.markerMove.setSymbol(markerMove.symbolNormal)
1067  self.markerMove = markerMove
1068  hPlot.replot()
1069 
1070  hPlot.canvas().setMouseTracking(False)
1071  self.draggingMode = True
1072 
1073  self.moveKnob.setVisible(True)
1074  self.moveKnob.setValue( markerMove.x )
1075 
1076  st = 'To move marker: Drag the mouse, use Left-arrow or Right-arrow keys, or use the bottom slider. Quit moving with Right-Click or Esc key.'
1077  self.statusBar.showMessage(st)
1078  hPlot.setStatusTip(st)
1079  else:
1080  return
1081 
1082 
1083  ##
1084  #
1085  # Deselect marker and mouse dragging.
1086  #
1087  def deselectMarker(self):
1088  nTab = self.tabWidget.currentIndex()
1089  self.hPlot[nTab].canvas().setMouseTracking(True)
1090  self.draggingMode = False
1091 
1092  if self.markerMove is not None: self.markerMove.setSymbol(self.markerMove.symbolNormal)
1093  self.markerMove = None
1094  self.moveKnob.setVisible(False)
1095  self.hPlot[nTab].replot()
1096 
1097  st = u'Left-click in a marker to move, Right-Click or Esc key to quit'
1098  self.statusBar.showMessage(st)
1099  self.hPlot[nTab].setStatusTip(st)
1100 
1101 
1102  ##
1103  #
1104  # Shifts marker if a marker is selected.
1105  # @param increment = Number of curve points to shift (int)
1106  #
1107  def shiftMarker(self, increment):
1108  if not self.actionMoveMarker.isChecked(): return # Only move markers when Move Marker action is checked
1109  if self.markerMove is None: return
1110 
1111  marker = self.markerMove
1112 
1113  # Check that marker index is not out of bounds
1114  N = len(marker.curve.data().xData())
1115  newindex = marker.index + increment
1116  if newindex < 0: newindex= 0
1117  if newindex > N-1: newindex = N-1
1118 
1119  marker.index = newindex
1120  marker.updatePosition()
1121  self.moveKnob.setValue(marker.x)
1122 
1123 
1124  ##
1125  #
1126  # Drags marker if a marker is selected.
1127  # @param point = Point coordinates (QPoint)
1128  #
1129  def dragMarker(self, point):
1130  if not self.actionMoveMarker.isChecked(): return # Only move markers when Move Marker action is checked
1131  if self.markerMove is None: return
1132 
1133  nTab = self.tabWidget.currentIndex()
1134  hPlot = self.hPlot[nTab]
1135  value = hPlot.invTransform(Qwt.QwtPlot.xBottom, point.x())
1136  marker = self.markerMove
1137 
1138 # # Vectorized version
1139 # xCurve = np.array(marker.curve.data().xData())
1140 # marker.index = np.argmin(np.abs(xCurve-value))
1141 
1142  # Faster version
1143  curve = marker.curve
1144  N = len(curve.data().xData())
1145  maxValue = curve.x(N-1)
1146  minValue = curve.x(0)
1147  marker.index = int(round( (N-1) * (value - minValue) / (maxValue - minValue) ))
1148 
1149  marker.updatePosition()
1150  self.moveKnob.setValue(marker.x)
1151  hPlot.replot()
1152 
1153 
1154  ##
1155  #
1156  # Add marker at the curve point with is closer to the mouse cursor, if distance is smaller than marker radius.
1157  # Both left-y and right-y axis curves are processed.
1158  # @param point = Point coordinates (QPoint)
1159  #
1160  def addMarker(self, point):
1161  if not self.actionNewMarker.isChecked(): return # Only add markers when New Marker action is checked
1162 
1163  nTab = self.tabWidget.currentIndex()
1164  hPlot = self.hPlot[nTab]
1165 
1166  scX, scY = point.x(), point.y()
1167 
1168 # # This is necessary only when point is in graph coordinates and must be transformed to canvas pixel coordinates.
1169 # scX = hPlot.transform(Qwt.QwtPlot.xBottom, point.x())
1170 # scY = hPlot.transform(Qwt.QwtPlot.yLeft, point.y())
1171 
1172  axis = None
1173  minDist = np.inf
1174 
1175  for CF in self.leftYCurves[nTab]: # We cannot vectorize processing curves since the may have different number of points
1176  if not CF.visible: continue # Do not add markers to non-visible curves
1177 
1178  for curve in CF.curveList:
1179  n, dist = curve.closestPoint(QPoint(scX, scY))
1180  if dist < minDist:
1181  minDist = dist
1182  curveMarker = curve
1183  nMarker = n
1184  axis= Qwt.QwtPlot.yLeft
1185 
1186  for CF in self.rightYCurves[nTab]: # We cannot vectorize processing curves since the may have different number of points
1187  if not CF.visible: continue # Do not add markers to non-visible curves
1188 
1189  for curve in CF.curveList:
1190  n, dist = curve.closestPoint(QPoint(scX, scY))
1191  if dist < minDist:
1192  minDist = dist
1193  curveMarker = curve
1194  nMarker = n
1195  axis= Qwt.QwtPlot.yRight
1196 
1197  if axis is None:
1198  QMessageBox.warning(self, "WARNING", 'There is not curve point within zoomed area. No marker added.')
1199  return
1200 
1201  # Threshold to select a point (in pixels), equal to curve symbol radius
1202  distThreshold = self.curveSymbolSize/2 if self.curveSymbolSize/2 >=7 else 7
1203  if minDist > distThreshold:
1204  QMessageBox.warning(self, "WARNING", 'No curve point selected.')
1205  return
1206 
1207  Marker = PlotMarker(self, axis, nTab, curveMarker, nMarker)
1208  Marker.attach(hPlot)
1209  hPlot.replot()
1210  self.markers[nTab].append(Marker)
1211 
1212 
1213  ##
1214  #
1215  # Reimplementation of the window close function.
1216  # Sets closed attribute to True.
1217  #
1218  def closeEvent(self, event):
1219  self.closed = True
1220  event.accept()
1221 
1222 
1223  ##
1224  #
1225  # Get actual max and min values of scaling for a given axis.
1226  # It is a separate function to encapsulate the different method names for qwt5.1 and qwt5.2
1227  # @param nTab = Tab index
1228  # @param axis = QwtPlot axis (Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom).
1229  # @return maxScale = Maximum scale value.
1230  # @return minScale = Minimum scale value.
1231  #
1232  def getScaleMaxMin(self, nTab, axis):
1233 
1234  try: # Attribute renamed from qwt5.1 to qwt5.2
1235  maxScale = self.hPlot[nTab].axisScaleDiv(axis).upperBound()
1236  minScale = self.hPlot[nTab].axisScaleDiv(axis).lowerBound()
1237  except AttributeError:
1238  maxScale = self.hPlot[nTab].axisScaleDiv(axis).hBound()
1239  minScale = self.hPlot[nTab].axisScaleDiv(axis).lBound()
1240 
1241  return maxScale, minScale
1242 
1243 
1244  ##
1245  #
1246  # Get max and min manual scaling values for a given axis.
1247  # @param nTab = tab index
1248  # @param axis = QwtPlot axis (Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom).
1249  # @return maxScale = Maximum scale value.
1250  # @return minScale = Minimum scale value.
1251  #
1252  def getManualScale(self, nTab, axis):
1253  if axis is Qwt.QwtPlot.yLeft:
1254  maxScale = self.scalingDlg[nTab].leftYmax_Counter.value()
1255  minScale = self.scalingDlg[nTab].leftYmin_Counter.value()
1256  elif axis is Qwt.QwtPlot.yRight:
1257  maxScale = self.scalingDlg[nTab].rightYmax_Counter.value()
1258  minScale = self.scalingDlg[nTab].rightYmin_Counter.value()
1259  elif axis is Qwt.QwtPlot.xBottom:
1260  maxScale = self.scalingDlg[nTab].Xmax_Counter.value()
1261  minScale = self.scalingDlg[nTab].Xmin_Counter.value()
1262 
1263  return maxScale, minScale
1264 
1265 
1266  ##
1267  #
1268  # Compute the required number of significant digits of markers labels, depending on axis scaling.
1269  # param nTab = Tab widget index.
1270  #
1271  def getPrecision(self, nTab):
1272  Xmax, Xmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.xBottom)
1273  leftYmax, leftYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yLeft)
1274 
1275  minPrec = 3 # Minimum number of significant digits
1276 
1277  if Xmax != 0.0:
1278  self.precX[nTab] = minPrec + int(ceil(log10(abs(Xmax))) - ceil(log10(abs(Xmax-Xmin))))
1279  else:
1280  self.precX[nTab] = minPrec
1281 
1282  if leftYmax != 0.0:
1283  self.precY[nTab] = minPrec + int(ceil(log10(abs(leftYmax))) - ceil(log10(abs(leftYmax-leftYmin))))
1284  else:
1285  self.precY[nTab] = minPrec
1286 
1287  # Update precision in marker labels
1288  for marker in self.markers[nTab]: marker.updateLabel()
1289 
1290 # print 'prec =', self.precX[nTab], self.precY[nTab]
1291 
1292 
1293  ##
1294  #
1295  # Update markerMove Knob limits according to actual xBottom scale
1296  #
1298  nTab = self.tabWidget.currentIndex()
1299  Xmax, Xmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.xBottom)
1300  self.moveKnob.setRange(Xmin, Xmax)
1301 
1302 
1303  ##
1304  #
1305  # Change visibility of curves of an existing plot.
1306  # @param nTab = Tab index
1307  # @param leftYVisible = List of visibility flags corresponding to leftYData curves (list of bool or None values).
1308  # Instead of a list of flags, it can be a dictionary of the form { curveName: flag, ... }. Curves corresponding to a missing dictionary key are not changed.
1309  # If a flag is None, the visibility of the corresponding curve is not changed.
1310  # If the parameter is equal to None, leftYData curves visibility is not changed. Default None.
1311  # @param rightYVisible = List of visibility flags corresponding to rightYData curves (list of bool or None values).
1312  # Instead of a list of flags, it can be a dictionary of the form { curveName: flag, ... }. Curves corresponding to a missing dictionary key are not changed.
1313  # If a flag is None, the visibility of the corresponding curve is not changed.
1314  # If the parameter is equal to None, rightYData curves visibility is not changed. Default None.
1315  # @param masksVisible = List of visibility flags corresponding to mask curves (list of bool or None values).
1316  # Instead of a list of flags, it can be a dictionary of the form { curveName: flag, ... }. Curves corresponding to a missing dictionary key are not changed.
1317  # If a flag is None, the visibility of the corresponding curve is not changed.
1318  # If the parameter is equal to None, mask curves visibility is not changed. Default None.
1319  #
1320  def changeCurveVisibility(self, nTab, leftYVisible = None, rightYVisible = None, masksVisible = None ):
1321  oldTab = self.tabWidget.currentIndex()
1322  self.tabWidget.setCurrentIndex(nTab)
1323 
1324  if type(leftYVisible).__name__ == 'list':
1325  for CF, flag in zip( self.leftYCurves[nTab], leftYVisible ):
1326  if flag is None: continue
1327  CF.setVisible(flag)
1328  CF.legendItem.setChecked(flag)
1329  for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items():
1330  if v == CF: k.setChecked(flag)
1331  elif type(leftYVisible).__name__ == 'dict':
1332  for (name, flag) in leftYVisible.items():
1333  if flag is None or name not in self.leftYNames[nTab]: continue
1334  CF = self.leftYCurves[nTab][self.leftYNames[nTab].index(name)]
1335  CF.setVisible(flag)
1336  CF.legendItem.setChecked(flag)
1337  for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items():
1338  if v == CF: k.setChecked(flag)
1339 
1340  if type(rightYVisible).__name__ == 'list':
1341  for CF, flag in zip( self.rightYCurves[nTab], rightYVisible ):
1342  if flag is None: continue
1343  CF.setVisible(flag)
1344  CF.legendItem.setChecked(flag)
1345  for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items():
1346  if v == CF: k.setChecked(flag)
1347  elif type(rightYVisible).__name__ == 'dict':
1348  for (name, flag) in rightYVisible.items():
1349  if flag is None or name not in self.rightYNames[nTab]: continue
1350  CF = self.rightYCurves[nTab][self.rightYNames[nTab].index(name)]
1351  CF.setVisible(flag)
1352  CF.legendItem.setChecked(flag)
1353  for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items():
1354  if v == CF: k.setChecked(flag)
1355 
1356  if type(masksVisible).__name__ == 'list':
1357  for curve, flag in zip( self.masks[nTab], masksVisible ):
1358  if flag is None: continue
1359  curve.setVisible(flag)
1360  self.legends[nTab].find(curve).setChecked(flag)
1361  for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items():
1362  if v == curve: k.setChecked(flag)
1363  elif type(masksVisible).__name__ == 'dict':
1364  for (name, flag) in masksVisible.items():
1365  if flag is None or name not in self.maskNames[nTab]: continue
1366  curve = self.masks[nTab][self.maskNames[nTab].index(name)]
1367  curve.setVisible(flag)
1368  self.legends[nTab].find(curve).setChecked(flag)
1369  for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items():
1370  if v == curve: k.setChecked(flag)
1371 
1372 
1373  self.hPlot[nTab].replot()
1374  self.tabWidget.setCurrentIndex(oldTab)
1375 
1376 
1377  ##
1378  #
1379  # Autoscale some axis.
1380  # This function is intended to be called after calling self.update when the programmer wishes to autoscale axis after update.
1381  # @param nTab = Tab index where to autoscale axis.
1382  # @param autoScaleX = Flag to indicate if bottom X axis must be autoscaled (True | False).
1383  # @param autoScaleLeftY = Flag to indicate if left Y axis must be autoscaled (True | False).
1384  # @param autoScaleRightY = Flag to indicate if right Y axis must be autoscaled (True | False).
1385  #
1386  def autoScaleSomeAxis(self, nTab, autoScaleBottomX, autoScaleLeftY, autoScaleRightY):
1387  zoomerBase = self.autoScaleBase[nTab]
1388 
1389  if autoScaleBottomX:
1390 # self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.xBottom) # There is no need to use Qwt autoscale, because autoscale values have already been stored in self.autoScaleBase[nTab]
1391  (axisMin, axisMax) = ( zoomerBase.Xmin, zoomerBase.Xmax )
1392  if axisMin != np.inf and axisMax != -np.inf: self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.xBottom, axisMin, axisMax )
1393  self.scalingDlg[nTab].X_groupBox.setChecked(False)
1394 
1395  if autoScaleLeftY:
1396 # self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yLeft) # There is no need to use Qwt autoscale, because autoscale values have already been stored in self.autoScaleBase[nTab]
1397  (axisMin, axisMax) = ( zoomerBase.leftYmin, zoomerBase.leftYmax )
1398  if axisMin != np.inf and axisMax != -np.inf: self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yLeft, axisMin, axisMax )
1399  self.scalingDlg[nTab].leftY_groupBox.setChecked(False)
1400 
1401  if autoScaleRightY:
1402 # self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yRight) # There is no need to use Qwt autoscale, because autoscale values have already been stored in self.autoScaleBase[nTab]
1403  (axisMin, axisMax) = ( zoomerBase.rightYmin, zoomerBase.rightYmax )
1404  if axisMin != np.inf and axisMax != -np.inf: self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yRight, axisMin, axisMax )
1405  self.scalingDlg[nTab].rightY_groupBox.setChecked(False)
1406 
1407  self.getPrecision(nTab)
1408  self.hPlot[nTab].replot()
1409 
1410  ##@{
1411  # @name Functions automatically executed when the user interacts with the GUI
1412 
1413 
1414  @pyqtSignature("double")
1415  ##
1416  #
1417  # This function is automatically executed when the user moves the moveKnob.
1418  # This slot must be manually connected to valueChanged, since the QwtKnob widget was not created in QtDesigner.
1419  # @param value = Knob value (double)
1420  #
1421  def on_moveKnob_valueChanged(self, value):
1422  if not self.actionMoveMarker.isChecked(): return
1423 
1424  nTab = self.tabWidget.currentIndex()
1425 
1426 # # Vectorized version
1427 # x = np.array(self.markerMove.curve.data().xData())
1428 # self.markerMove.index = np.argmin(np.abs(x-value))
1429 
1430  # Faster version
1431  curve = self.markerMove.curve
1432  N = len(curve.data().xData())
1433  maxValue = curve.x(N-1)
1434  minValue = curve.x(0)
1435  self.markerMove.index = int(round( (N-1) * (value - minValue) / (maxValue - minValue) ))
1436 
1437  self.markerMove.updatePosition()
1438  self.hPlot[nTab].replot()
1439 
1440 
1441  ##
1442  #
1443  # Change curve visibility by using the EditCurvesDlg dialog.
1444  # self.setCurves.curvesCheckBoxesDict is used to find the QwtPlotCurve corresponding to the self.setCurves checkBox that sent the signal.
1445  # @param value = State of the checkBox that sent the signal.
1446  #
1447  def setCurveVisibility(self, value):
1448  on = value != 0
1449  nTab = self.tabWidget.currentIndex()
1450  CF = self.setCurves[nTab].curvesCheckBoxesDict[self.sender()]
1451  CF.setVisible(on)
1452 
1453  # Update markers visibility
1454  for curve in CF.curveList:
1455  for marker in self.markers[nTab]:
1456  if marker.curve == curve: marker.setVisible(on)
1457 
1458  # Update legend item check state
1459  CF.legendItem.setChecked(on)
1460 
1461  self.hPlot[nTab].replot()
1462 
1463 
1464  ##
1465  #
1466  # Change curve pen width by using the EditCurvesDlg dialog.
1467  # self.setCurves.curvesCheckBoxesDict is used to find the QwtPlotCurve corresponding to the self.setCurves spinBox that sent the signal.
1468  # @param value = Value of the spinBox that sent the signal.
1469  #
1470  def setCurveWidth(self, value):
1471  nTab = self.tabWidget.currentIndex()
1472  CF = self.setCurves[nTab].curvesSpinBoxesDict[self.sender()]
1473  pen = CF.pen
1474  pen.setWidth(value)
1475  CF.setPen(pen)
1476  self.hPlot[nTab].replot()
1477 
1478 
1479  ##
1480  #
1481  # Change curve pen color by using the EditCurvesDlg dialog.
1482  # self.setCurves.curvesPushButtonsDict is used to find the QwtPlotCurve corresponding to the self.setCurves pushButton that sent the signal.
1483  # @param checked = Check state of the pushButton that sent the signal.
1484  #
1485  def setCurveColor(self, checked):
1486  nTab = self.tabWidget.currentIndex()
1487  CF = self.setCurves[nTab].curvesPushButtonsDict[self.sender()]
1488  pen = CF.pen
1489  newColor = QColorDialog().getColor(pen.color())
1490  palette = self.sender().palette()
1491  palette.setColor(QPalette.Button, newColor)
1492  self.sender().setPalette(palette)
1493  pen.setColor(newColor)
1494  CF.setPen(pen)
1495  self.hPlot[nTab].replot()
1496 
1497 
1498  ##
1499  #
1500  # Change curve visibility by checking legend items.
1501  # @param value = State of the legendItem that sent the signal.
1502  #
1503  def showCurve(self, item, on):
1504  nTab = self.tabWidget.currentIndex()
1505  item.setVisible(on)
1506 
1507  # Update EditCurvesDlg checkBox
1508  for k, CF in self.setCurves[nTab].curvesCheckBoxesDict.items():
1509  if CF.curveList[0] == item: k.setChecked(on)
1510 
1511  self.hPlot[nTab].replot()
1512 
1513 
1514  ##
1515  #
1516  # Change scaling of a given axis according to manual scaling control values.
1517  # @param axis = QwtPlot axis (Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom).
1518  #
1519  def updateManualAxisScaling(self, axis):
1520  nTab = self.tabWidget.currentIndex()
1521  maxScale, minScale = self.getManualScale(nTab, axis)
1522  self.hPlot[nTab].setAxisScale(axis, minScale, maxScale)
1523  self.getPrecision(nTab)
1524  self.hPlot[nTab].replot()
1525 
1526 
1527  ##
1528  #
1529  # Set manaul/auto axis scaling.
1530  # @param value = Value of axis manual scaling group CheckBox (int). 0 -> False (auto), 2 -> True (manual).
1531  # @param axis = QwtPlot axis (Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom).
1532  #
1533  def setManualScaling(self, value, axis):
1534  nTab = self.tabWidget.currentIndex()
1535 
1536  if value > 0:
1537  self.updateManualAxisScaling(axis)
1538  # Do not try to autoscale if value<0 (user uncheck groupBox).
1539  # This would result in autoscaling when user clicks zoom toolbar icon, without right-click,
1540  # because on_actionZoom_toggled() sets XXXXgroupBox.setChecked(False)
1541  # which is connected to setManualScaling(False, axis)
1542 
1543  self.hPlot[nTab].replot()
1544 
1545 
1546  ##
1547  # Axis auto scake.
1548  # This function is automatically executed by the GUI when the user presses the 'Auto' buttom of some axis.
1549  # @param axis = QwtPlot axis (Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom)
1550  # @param groupBox = Manual scaling groupBox, that will be unchecked.
1551  #
1552  def setAutoScaling(self, axis, groupBox):
1553  nTab = self.tabWidget.currentIndex()
1554  groupBox.setChecked(False) # Disable manual scaling
1555 
1556  zoomerBase = self.autoScaleBase[nTab]
1557  if axis == Qwt.QwtPlot.yLeft: (axisMin, axisMax) = ( zoomerBase.leftYmin, zoomerBase.leftYmax )
1558  if axis == Qwt.QwtPlot.yRight: (axisMin, axisMax) = ( zoomerBase.rightYmin, zoomerBase.rightYmax )
1559  if axis == Qwt.QwtPlot.xBottom: (axisMin, axisMax) = ( zoomerBase.Xmin, zoomerBase.Xmax )
1560 
1561  if axisMin != np.inf and axisMax != -np.inf: self.hPlot[nTab].setAxisScale( axis, axisMin, axisMax )
1562  self.getPrecision(nTab)
1563  self.hPlot[nTab].replot()
1564 
1565 
1566  @pyqtSignature("int")
1567  ##
1568  #
1569  # Disable main window actions actionZoom and actionCurves.
1570  # This function is automatically executed by the GUI when the user changes the current tab page.
1571  #
1573  nTab = self.tabWidget.currentIndex()
1574 
1575  if nTab >= len(self.hPlot): return # The function was automatically called when the tab was added and the plots have not been created yet
1576 
1577  # Hide previous setCurvesDlg
1578  if self.setCurvesCurrent is not None: self.setCurvesCurrent.hide()
1579 
1580  # Change scale Dlg
1581  if self.scalingDlgCurrent is not None:
1582  geom = self.scalingDlgCurrent.geometry()
1583  self.scalingDlgCurrent.hide()
1584  self.on_actionScaleAxis_toggled(True)
1585  self.scalingDlgCurrent.setGeometry(geom)
1586 
1587  # Update markerMove Knob limits
1588  self.updateMoveKnobRange()
1589 
1590  self.on_actionZoom_toggled(False)
1591  self.on_actionNewMarker_toggled(False)
1592  self.on_actionDeleteMarker_toggled(False)
1593  self.on_actionMoveMarker_toggled(False)
1594  self.actionCurves.setChecked(False)
1595 
1596  if self.setCurves[nTab] is not None:
1597  self.on_actionCurves_toggled(False)
1598  self.actionCurves.setEnabled(True)
1599  else:
1600  self.actionCurves.setEnabled(False)
1601 
1602 
1603  @pyqtSignature("bool")
1604  ##
1605  #
1606  # Print [S] parameters plot.
1607  # This function is automatically executed by the GUI when the user triggers the 'Print' action.
1608  #
1609  def on_actionPrint_triggered(self, checked):
1610  nTab = self.tabWidget.currentIndex()
1611 
1612  printer = QPrinter(QPrinter.HighResolution)
1613 
1614  printer.setOrientation(QPrinter.Landscape)
1615  printer.setColorMode(QPrinter.Color)
1616 
1617  dialog = QPrintDialog(printer)
1618  if dialog.exec_():
1619  filter = PrintFilter()
1620  if (QPrinter.GrayScale == printer.colorMode()):
1621  filter.setOptions(QwtPlotPrintFilter.PrintAll & ~QwtPlotPrintFilter.PrintBackground | QwtPlotPrintFilter.PrintFrameWithScales)
1622  self.hPlot[nTab].print_(printer, filter)
1623 
1624 
1625  @pyqtSignature("bool")
1626  ##
1627  #
1628  # Display About dialog.
1629  # This function is automatically executed by the GUI when the user triggers the 'About' action.
1630  # The information shown is read from the file docstring.
1631  #
1632  def on_actionAbout_triggered(self, checked):
1633  st = __doc__
1634  st = st.replace('@package', 'Package:')
1635  st = st.replace('@author', 'Authors:')
1636  st = st.replace('@date', 'Date:')
1637  st = st.replace('@version', 'Version:')
1638 
1639  st = u"""<b>%s</b>
1640  <p>%s
1641  <p>Software versions:
1642  <p>Python %s - Qt %s - PyQt %s - QWT %s on %s %s""" % (applicationName, st, platform.python_version(),QT_VERSION_STR, PYQT_VERSION_STR, Qwt.QWT_VERSION_STR, platform.system(), platform.release())
1643 
1644  QMessageBox.about(self, "About %s" % applicationName, st)
1645 
1646 
1647  @pyqtSignature("bool")
1648  ##
1649  #
1650  # Save [S] parameters plot to PDF file.
1651  # This function is automatically executed by the GUI when the user triggers the 'PDF' action.
1652  #
1653  def on_actionPDF_triggered(self, checked):
1654  nTab = self.tabWidget.currentIndex()
1655 
1656  if QT_VERSION > 0x040100:
1657  fileName = QFileDialog.getSaveFileName(self,
1658  'Export File Name', 'dbPlot.pdf', 'PDF Documents (*.pdf)')
1659 
1660  if not fileName.isEmpty():
1661  printer = QPrinter()
1662  printer.setOutputFormat(QPrinter.PdfFormat)
1663  printer.setOrientation(QPrinter.Landscape)
1664  printer.setOutputFileName(fileName)
1665 
1666  printer.setCreator('lossyfiltersgui')
1667  self.hPlot[nTab].print_(printer)
1668 
1669 
1670  @pyqtSignature("bool")
1671  ##
1672  # Enable or disable zoom.
1673  # This function is automatically executed by the GUI when the user toggles the 'Zoom' button.
1674  #
1675  def on_actionZoom_toggled(self, checked):
1676  nTab = self.tabWidget.currentIndex()
1677  if nTab >= len(self.hPlot): return # The function was automatically called when the tab was added and the plots have not been created yet
1678 
1679  #checked = self.actionZoom.isChecked()
1680  self.zoomerLeft[nTab].setEnabled(checked)
1681  self.zoomerRight[nTab].setEnabled(checked)
1682 
1683  if checked:
1684  st = u'Drag rectangle to zoom in, center-click to zoom back, shift-center-click to zoom forward, right-click to autoscale all axis'
1685  self.on_actionScaleAxis_toggled(False)
1686  self.actionScaleAxis.setChecked(False)
1687 
1688  # Disable adding and deleting markers
1689  self.actionNewMarker.setChecked(False)
1690  self.actionDeleteMarker.setChecked(False)
1691  self.actionMoveMarker.setChecked(False)
1692 
1693  # Disable manual scaling
1694  self.scalingDlg[nTab].leftY_groupBox.setChecked(False)
1695  self.scalingDlg[nTab].rightY_groupBox.setChecked(False)
1696  self.scalingDlg[nTab].X_groupBox.setChecked(False)
1697  else:
1698  self.actionZoom.setChecked(False)
1699  st = u'Use toolbar buttons to zoom, add or delete markers'
1700 
1701  if not self.actionNewMarker.isChecked(): # Do not change status bar when this function is executed by on_actionNewMarker_toggled(True)
1702  self.statusBar.showMessage(st)
1703  self.hPlot[nTab].setStatusTip(st)
1704 
1705 
1706  @pyqtSignature("bool")
1707  ##
1708  # Allow adding markers with mouse click.
1709  # This function is automatically executed by the GUI when the user toggles the 'New Marker' button.
1710  #
1711  def on_actionNewMarker_toggled(self, checked):
1712  nTab = self.tabWidget.currentIndex()
1713 
1714  if checked:
1715  st = u"Left-Click in a curve point to add a marker, Right-Click in background or press 'Del' key to delete last marker "
1716 
1717  # Disable zoom and deleting markers
1718  self.on_actionZoom_toggled(False)
1719  self.actionDeleteMarker.setChecked(False)
1720  self.actionMoveMarker.setChecked(False)
1721 
1722  self.pickerLeft[nTab].setTrackerPen(QPen(Qt.blue))
1723 
1724  symbol = Qwt.QwtSymbol(Qwt.QwtSymbol.Ellipse,QBrush(Qt.yellow), QPen(Qt.red, 2), QSize(5, 5) )
1725  for CF in self.leftYCurves[nTab] + self.rightYCurves[nTab]:
1726  pen = CF.pen
1727  self.curveSymbolSize = pen.width()*5 if pen.width() != 0 else 5
1728  symbol.setPen(pen)
1729  symbol.setSize(self.curveSymbolSize , self.curveSymbolSize )
1730  CF.setSymbol(symbol)
1731 
1732  else:
1733  st = u'Use toolbar buttons to zoom, add, move or delete markers'
1734  self.actionNewMarker.setChecked(False)
1735 
1736  self.pickerLeft[nTab].setTrackerPen(QPen(Qt.black))
1737 
1738  symbol = Qwt.QwtSymbol(Qwt.QwtSymbol.NoSymbol,QBrush(Qt.yellow), QPen(Qt.red, 2), QSize(5, 5) )
1739 
1740  for CF in self.leftYCurves[nTab]:
1741  CF.setSymbol(symbol)
1742 
1743  for CF in self.rightYCurves[nTab]:
1744  CF.setSymbol(symbol)
1745 
1746  self.statusBar.showMessage(st)
1747  self.hPlot[nTab].setStatusTip(st)
1748  self.hPlot[nTab].replot()
1749 
1750 
1751  @pyqtSignature("bool")
1752  ##
1753  # Delete markers with mouse click.
1754  # This function is automatically executed by the GUI when the user toggles the 'Delete Marker' button.
1755  #
1756  def on_actionDeleteMarker_toggled(self, checked):
1757  nTab = self.tabWidget.currentIndex()
1758 
1759  if checked:
1760 
1761  # Disable zoom and adding markers
1762  self.on_actionZoom_toggled(False)
1763  self.actionNewMarker.setChecked(False)
1764  self.actionMoveMarker.setChecked(False)
1765 
1766  if len(self.markers[nTab]) == 0:
1767  QMessageBox.warning(self, "WARNING", 'There are no markers to delete in this Tab.')
1768  self.on_actionDeleteMarker_toggled(False)
1769  return
1770 
1771  self.pickerLeft[nTab].setTrackerMode(Qwt.QwtPicker.AlwaysOff)
1772  st = u'Left-click in a marker to delete, Right-Click or Esc key to quit.'
1773  else:
1774  self.pickerLeft[nTab].setTrackerMode(Qwt.QwtPicker.AlwaysOn)
1775  self.pickerLeft[nTab].setTrackerPen(QPen(Qt.black))
1776  st = u'Use toolbar buttons to zoom, add, move or delete markers'
1777  self.actionDeleteMarker.setChecked(False)
1778 
1779  self.statusBar.showMessage(st)
1780  self.hPlot[nTab].setStatusTip(st)
1781 
1782 
1783  @pyqtSignature("bool")
1784  ##
1785  # Move marker.
1786  # This function is automatically executed by the GUI when the user toggles the 'Move Marker' button.
1787  #
1788  def on_actionMoveMarker_toggled(self, checked):
1789  nTab = self.tabWidget.currentIndex()
1790 
1791  if checked:
1792  # Disable zoom and adding markers
1793  self.on_actionZoom_toggled(False)
1794  self.actionNewMarker.setChecked(False)
1795  self.actionDeleteMarker.setChecked(False)
1796 
1797  if len(self.markers[nTab]) == 0:
1798  QMessageBox.warning(self, "WARNING", 'There are no markers to move in this Tab.')
1799  self.on_actionMoveMarker_toggled(False)
1800  return
1801 
1802  self.pickerLeft[nTab].setTrackerMode(Qwt.QwtPicker.AlwaysOff)
1803  st = u'Left-click in a marker to move, Right-Click or Esc key to quit'
1804 
1805  # If there is only one marker, select it automatically for movement
1806  if len(self.markers[nTab]) == 1: self.hightlightMarker(nTab, self.markers[nTab][0])
1807 
1808  else:
1809  self.deselectMarker()
1810 
1811  self.pickerLeft[nTab].setTrackerMode(Qwt.QwtPicker.AlwaysOn)
1812  self.pickerLeft[nTab].setTrackerPen(QPen(Qt.black))
1813 
1814  self.moveKnob.setVisible(False)
1815  self.actionMoveMarker.setChecked(False)
1816  st = u'Use toolbar buttons to zoom, add, move or delete markers'
1817 
1818  self.statusBar.showMessage(st)
1819  self.hPlot[nTab].setStatusTip(st)
1820 
1821 
1822  @pyqtSignature("bool")
1823  ##
1824  # Set curves visibility.
1825  # This function is automatically executed by the GUI when the user toggles the 'Set curves visibility' button.
1826  #
1827  def on_actionCurves_toggled(self, checked):
1828  nTab = self.tabWidget.currentIndex()
1829  if nTab >= len(self.hPlot): return # The function was automatically called when the tab was added and the plots have not been created yet
1830  if self.setCurves[nTab] is None: return # The function was automatically called for a tab page that has no EditCurvesDlg
1831 
1832  if checked:
1833  self.setCurves[nTab].show()
1834  self.setCurves[nTab].raise_()
1835  self.setCurves[nTab].activateWindow()
1836  self.setCurvesCurrent = self.setCurves[nTab]
1837  else:
1838  self.setCurves[nTab].hide()
1839 
1840 
1841  @pyqtSignature("bool")
1842  ##
1843  # Axis scaling.
1844  # This function is automatically executed by the GUI when the user toggles the 'Axis scale' button.
1845  #
1846  def on_actionScaleAxis_toggled(self, checked):
1847  nTab = self.tabWidget.currentIndex()
1848  if nTab >= len(self.hPlot): return # The function was automatically called when the tab was added and the plots have not been created yet
1849  if self.scalingDlg[nTab] is None: return # The function was automatically called for a tab page that has no EditCurvesDlg
1850 
1851  if checked:
1852  self.scalingDlg[nTab].show()
1853  self.scalingDlg[nTab].raise_()
1854  self.scalingDlg[nTab].activateWindow()
1855  self.scalingDlgCurrent = self.scalingDlg[nTab]
1856  self.on_actionZoom_toggled(False)
1857  else:
1858  self.scalingDlg[nTab].hide()
1859  self.scalingDlgCurrent = None
1860 
1861 
1862  ##@}
1863 
1864 
1865 ##
1866 #
1867 # GUI dialog to contain axis scaling controls.
1868 # @param parent = Parent widget, in this case is mainWindow. Default None.
1869 #
1870 class AxisScalingDlg(QDialog, Ui_dbplot_scaling.Ui_scaling_Dialog):
1871  ##
1872  #
1873  # Constructor: Creates PyQt4.QtGui.Qdialog window from QtDesigner.
1874  #
1875  def __init__(self, parent = None):
1876  super(AxisScalingDlg, self).__init__(parent)
1877  self.setupUi(self)
1878  self.mainWindow = parent
1879 
1880  ##
1881  #
1882  # Reimplementation of the window close function.
1883  # Sets scalingDlgCurrent attribute to None and hide the window.
1884  #
1885  def closeEvent(self, event):
1886  nTab = self.mainWindow.tabWidget.currentIndex()
1887  self.mainWindow.scalingDlg[nTab].hide()
1888  self.mainWindow.scalingDlgCurrent = None
1889  self.mainWindow.actionScaleAxis.setChecked(False)
1890  # Do not close with, just hiding is enough event.accept()
1891 
1892 
1893 ##
1894 #
1895 # Data structure containing info about properties of a family of curves and the curve list.
1896 # A curve family is a list of curves sharing the same properties.
1897 # The Y data for a curve family is defined in the columns of a 2-D numpy array.
1898 #
1899 # The class attributes and methods will not be documented, because the names are self-explanatory and the code is trivial.
1900 #
1901 class CurveFamily(object):
1902 
1903  ##
1904  #
1905  # Class constructor.
1906  # @param hPlot
1907  # @param name
1908  # @param xdata
1909  # @param ydata
1910  # @param axis
1911  # @param visible
1912  # @param color
1913  # @param width
1914  #
1915  def __init__(self, hPlot, name, xdata, ydata, axis, visible, color, width):
1916  self.hPlot = hPlot
1917  self.name = name
1918  self.axis = axis
1919  self.visible = visible
1920  self.Ncurves = 0 # For the moment, it is empty
1921  self.legend = None # For the moment, there is no legend
1922  self.legendItem = None
1923 
1924  if width is not None: self.pen = QPen(color, width)
1925  else: self.pen = QPen(color)
1926 
1927  self.newCurves(xdata, ydata)
1928 
1929 
1930  ##
1931  #
1932  # Create new list of curves.
1933  # @param xdata
1934  # @param ydata
1935  #
1936  def newCurves(self, xdata, ydata):
1937  # Check if there is a family of curves in a 2-D array
1938  if type(ydata).__name__ == 'ndarray' and ydata.ndim == 2:
1939  self.Ncurves = ydata.shape[1]
1940  else:
1941  self.Ncurves = 1
1942 
1943  self.curveList = []
1944  for m in range(self.Ncurves):
1945  curve = Qwt.QwtPlotCurve(self.name)
1946  curve.attach(self.hPlot)
1947  if self.Ncurves == 1: curve.setData(xdata, ydata)
1948  else: curve.setData(xdata, ydata[:, m])
1949  curve.setPen(self.pen)
1950  curve.setYAxis(self.axis)
1951  curve.setVisible(self.visible)
1952  self.curveList.append(curve)
1953 
1954 
1955  ##
1956  #
1957  # Delete all curves.
1958  #
1959  def deleteCurves(self):
1960  for curve in self.curveList:
1961  curve.detach()
1962  del curve
1963  self.curveList = []
1964  self.Ncurves = 0
1965 
1966 
1967  ##
1968  #
1969  # Update all curves with new data.
1970  # @param xdata
1971  # @param ydata
1972  #
1973  def updateCurves(self, xdata, ydata):
1974  # Check if there is a family of curves in a 2-D array
1975  if type(ydata).__name__ == 'ndarray' and ydata.ndim == 2: Ncurves = ydata.shape[1]
1976  else: Ncurves = 1
1977 
1978  # Max and min coordinates for this curve family. Necessary to update zoomerBase in DbPlot.update()
1979  cfXmin, cfXmax, cfYmin, cfYmax = np.inf, -np.inf, np.inf, -np.inf
1980 
1981  if Ncurves != self.Ncurves: # We cannot update the data directly, we have to delete and set the curves again.
1982  self.deleteCurves()
1983  self.newCurves(xdata, ydata)
1984  self.setLegendItems()
1985  dataUpdated = True
1986  else:
1987  dataUpdated = False
1988 
1989  for m in range(Ncurves): # We have to run this loop even if the data is already updated, in order to find min and max coordinates
1990  if Ncurves == 1: y = ydata
1991  else: y = ydata[:, m]
1992  if not dataUpdated: self.curveList[m].setData(xdata, y)
1993 
1994  if min(xdata) < cfXmin: cfXmin = min(xdata)
1995  if max(xdata) > cfXmax: cfXmax = max(xdata)
1996  if min(y) < cfYmin: cfYmin = min(y)
1997  if max(y) > cfYmax: cfYmax = max(y)
1998 
1999  return cfXmin, cfXmax, cfYmin, cfYmax
2000 
2001 
2002  ##
2003  #
2004  # Set QPen for all curves.
2005  # @param pen
2006  #
2007  def setPen(self, pen):
2008  self.pen = pen
2009  for curve in self.curveList: curve.setPen(self.pen)
2010 
2011 
2012  ##
2013  #
2014  # Set visibility of all curves.
2015  # @param visible = Visibility flag (Bool)
2016  #
2017  def setVisible(self, visible):
2018  self.visible = visible
2019  for curve in self.curveList: curve.setVisible(self.visible)
2020 
2021 
2022  ##
2023  #
2024  # Set symbol for all curves.
2025  # @param symbol
2026  #
2027  def setSymbol(self, symbol):
2028  self.symbol = symbol
2029  for curve in self.curveList: curve.setSymbol(self.symbol)
2030 
2031 
2032  ##
2033  #
2034  # After creating a legend for the QWtPlot, this method will assign the QwtLegendItem of the first curve
2035  # to self.legendItem, and remove the other QwtLegendItem's from the QwtLegend layout.
2036  # @param legend = QwtLegend in QWtPlot. If equal to None, the curves are being updated and the QwtLegend handle is already stored in self.legend .
2037  # Default None.
2038  # @param updateLegend = Call curve.updateLegend() for the 1st curve. Default False.
2039  #
2040  def setLegendItems(self, legend = None, updateLegend=False):
2041  if legend is not None: self.legend = legend
2042 
2043  # The legendItem for the curve family is the legendItem of the 1st curve.
2044  # The legendItem's for the other curves will be removed from the layout and deleted.
2045  self.legendItem = self.legend.find(self.curveList[0])
2046 
2047  for n, curve in enumerate(self.curveList):
2048  item = self.legend.find(curve)
2049 
2050  if n==0: # 1st curve
2051  idenWidth = item.identifierWidth()
2052  item.setIdentifierWidth(2*idenWidth)
2053  item.setChecked(self.visible)
2054  if updateLegend: curve.updateLegend(self.legend)
2055  else: # Remove the legendItem form the layout and delete it
2056  item.setVisible(False) # IMPORTANT: It does not work without this statement, although it may seem irrelevant.
2057  layout = self.legend.contentsWidget().children()[0]
2058  layout.removeWidget(item)
2059  del item
2060 
2061 
2062 ##
2063 #
2064 # Dialog to select visible curves.
2065 #
2066 class EditCurvesDlg(QDialog):
2067 
2068  ##
2069  #
2070  # Create dialog to select visible curves.
2071  # @param leftYCurves = List of QwtPlotCurves in left y-axis.
2072  # @param rightYCurves = List of QwtPlotCurves in right y-axis.
2073  # @param parent = Parent window (default None).
2074  #
2075  def __init__(self, leftYCurves, rightYCurves, parent=None):
2076  super(EditCurvesDlg, self).__init__(parent)
2077 
2078  ##
2079  # Handle to the dbplot main window.
2080  self.mainWindow = parent
2081 
2082  # #
2083  # Dictionary with curves corresponding to each checkBox widget
2084  self.curvesCheckBoxesDict = {}
2085 
2086  ##
2087  #Dictionary with curves corresponding to each spinBox widget
2088  self.curvesSpinBoxesDict = {}
2089 
2090  ##
2091  #Dictionary with curves corresponding to each pushBotton widget
2092  self.curvesPushButtonsDict = {}
2093 
2094  nTab = self.mainWindow.tabWidget.currentIndex()
2095 
2096  grid = QGridLayout()
2097 
2098  if len(leftYCurves) > 0:
2099  grid.addWidget(QLabel("<b>Left-Y curves</b>"), 0, 0, 1, -1, Qt.AlignHCenter)
2100  grid.addWidget(QLabel("Curve"), 1, 0, Qt.AlignHCenter)
2101  grid.addWidget(QLabel("Width"), 1, 1, Qt.AlignHCenter)
2102  grid.addWidget(QLabel("Color"), 1, 2, Qt.AlignHCenter)
2103 
2104  for n in range(len(leftYCurves)):
2105  CF = leftYCurves[n]
2106  checkBox = QCheckBox(CF.name.replace('<sub>', '').replace('</sub>', ''))
2107  checkBox.setChecked(CF.visible)
2108  self.curvesCheckBoxesDict[checkBox] = CF
2109  self.connect(checkBox, SIGNAL("stateChanged(int)"), self.mainWindow.setCurveVisibility )
2110  grid.addWidget(checkBox, n+2, 0)
2111  spinBox = QSpinBox()
2112  spinBox.setMinimum(0)
2113  spinBox.setValue(CF.pen.width())
2114  self.curvesSpinBoxesDict[spinBox] = CF
2115  self.connect(spinBox, SIGNAL("valueChanged(int)"), self.mainWindow.setCurveWidth )
2116  grid.addWidget(spinBox, n+2, 1)
2117  pushButton = QPushButton()
2118  palette = pushButton.palette()
2119  palette.setColor(QPalette.Button, CF.pen.color())
2120  pushButton.setPalette(palette)
2121  self.curvesPushButtonsDict[pushButton] = CF
2122  self.connect(pushButton, SIGNAL("clicked(bool)"), self.mainWindow.setCurveColor )
2123  grid.addWidget(pushButton, n+2, 2)
2124 
2125  nLeft = n
2126 
2127  if len(rightYCurves) > 0:
2128  grid.addWidget(QLabel(" "), n+3, 0, 1, -1, Qt.AlignHCenter) # Empty row
2129  grid.addWidget(QLabel("<b>Right-Y curves</b>"), n+4, 0, 1, -1, Qt.AlignHCenter)
2130  grid.addWidget(QLabel("Curve"), n+5, 0, Qt.AlignHCenter)
2131  grid.addWidget(QLabel("Width"), n+5, 1, Qt.AlignHCenter)
2132  grid.addWidget(QLabel("Color"), n+5, 2, Qt.AlignHCenter)
2133 
2134  for n in range(len(rightYCurves)):
2135  CF = rightYCurves[n]
2136  Hlayout = QHBoxLayout()
2137  checkBox = QCheckBox(CF.name.replace('<sub>', '').replace('</sub>', ''))
2138  checkBox.setChecked(CF.visible)
2139  self.curvesCheckBoxesDict[checkBox] = CF
2140  self.connect(checkBox, SIGNAL("stateChanged(int)"), self.mainWindow.setCurveVisibility )
2141  grid.addWidget(checkBox, nLeft+6+n, 0)
2142  spinBox = QSpinBox()
2143  spinBox.setMinimum(0)
2144  spinBox.setValue(CF.pen.width())
2145  self.curvesSpinBoxesDict[spinBox] = CF
2146  self.connect(spinBox, SIGNAL("valueChanged(int)"), self.mainWindow.setCurveWidth )
2147  grid.addWidget(spinBox, nLeft+6+n, 1)
2148  pushButton = QPushButton()
2149  palette = pushButton.palette()
2150  palette.setColor(QPalette.Button, CF.pen.color())
2151  pushButton.setPalette(palette)
2152  self.curvesPushButtonsDict[pushButton] = CF
2153  self.connect(pushButton, SIGNAL("clicked(bool)"), self.mainWindow.setCurveColor )
2154  grid.addWidget(pushButton, nLeft+6+n, 2)
2155 
2156  self.setLayout(grid)
2157  self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
2158  self.setWindowTitle("Curves")
2159  self.setToolTip("Select visible curves")
2160 
2161  ##
2162  #
2163  # Reimplementation of the dialog close function.
2164  # Uncheck the toolbar action.
2165  #
2166  def closeEvent(self, event):
2167  self.mainWindow.actionCurves.setChecked(False)
2168  event.accept()
2169 
2170 
2171 ##
2172 #
2173 # Class derived from Qwt.QwtPlotPicker to allow reimplementation of Qwt.QwtPlotPicker.trackerText()
2174 #
2175 class MyPicker(Qwt.QwtPlotPicker):
2176  ##
2177  #
2178  # Derived class constructor.
2179  # Saves dbplot and nTab as class attributes.
2180  # @param dbplot = DbPlot class instance containing the Tab where this QwtPlotPicker exists.
2181  # @param nTab = Tab widget index containing this QwtPlotPicker.
2182  #
2183  def __init__(self, dbplot, nTab, *args):
2184  super(MyPicker, self).__init__(*args)
2185  self.dbplot = dbplot
2186  self.nTab = nTab
2187 
2188  ##
2189  #
2190  # Reimplementation of tracker text function, to customize tracker appereance
2191  #
2192  def trackerText(self, point):
2193  # Check if its necessary to convert from pixel to axis coordinates.
2194  # This is necessary since this function is overloaded in Qwt
2195  # QwtText trackerText (const QPoint &) const
2196  # QwtText trackerText (const QwtDoublePoint &) const
2197  if type(point).__name__ == "QPoint": point = self.invTransform(point)
2198  st = '%.*g%s, %.*g%s' % (self.dbplot.precX[self.nTab], point.x(), self.dbplot.Xunits[self.nTab], self.dbplot.precY[self.nTab], point.y(), self.dbplot.leftYunits[self.nTab])
2199  return Qwt.QwtText(st)
2200 
2201 
2202 ##
2203 #
2204 # Event filter for the canvas.
2205 #
2206 # This filter is necessary to pick up events that are not handled by the QwtPlotPicker class, like mouse dragging.
2207 #
2208 # It would be enough (and better practice) to reimplement mousePressEvent() and keyPressEvent() of the QwtPlotCanvas class,
2209 # but we if we create a derived class from QwtPlotCanvas it is not possible to set an instance of this class to the canvas of a QwtPlot object
2210 # since there is not setPlotCanvas() method in QwtPlot class.
2211 #
2212 # To handle left mouse clicks it is enough to create a mouseClicked() slot and connect to 'selected' signal of QwtPlotPicker class:
2213 # self.connect(pickerLeft, SIGNAL('selected(QwtDoublePoint)'), self.mouseClicked )
2214 # but since we have to implement this filter anyway in order to handle mouse dragging, we will handle also all the other events here.
2215 #
2216 # Reimplementation of mousePressEvent() from QwtPlot does not work, because we need the mouse coordinates obtained from event.pos() to be relative to the
2217 # QwtPlotCanvas widget, not to the QwtPlot, in order to get the graph coordinates using QwtPlot.invTransform().
2218 #
2219 # Implementing this filter is essentially what the QwtPlotPicker class does, but unfortunately it does not support mouse dragging, only mouse selection.
2220 #
2221 class CanvasEventFilter(QObject):
2222 
2223  ##
2224  #
2225  # Class constructor.
2226  # @param dbplot = DbPlot class instance, necessary to check the value of some dbPlot flags.
2227  # @param hPlot = The parent widget. Must be a Qwt.QwtPlot class instance, since non-processed events will be sent to Qwt.QwtPlot.eventFilter(self, object, event).
2228  #
2229  def __init__(self, dbplot, hPlot):
2230  # Initialise parent
2231  QObject.__init__(self, hPlot) # This was in pyQwt example
2232  #super(CanvasEventFilter, self).__init__(*args) # ESTA NO FUNCIONA (y es aparentemente lo mismo)
2233 
2234  self.dbplot = dbplot
2235 
2236  ##
2237  #
2238  # The event filter.
2239  # When the event has been processed, for mouse movements the return value is not True,
2240  # in order to allow the event to be processed also by other widgets like QwtPlotPicker.
2241  #
2242  def eventFilter(self, object, event):
2243 
2244  if event.type() == QEvent.MouseButtonPress:
2245 
2246  if event.button() == Qt.RightButton:
2247 
2248  if self.dbplot.actionNewMarker.isChecked():
2249  if len(self.dbplot.markers[self.dbplot.tabWidget.currentIndex()]) > 0: # If there are markers, delete the last one.
2250  self.dbplot.deleteLastMarker()
2251  else: # Quit
2252  self.dbplot.on_actionNewMarker_toggled(False)
2253  return True
2254 
2255  if self.dbplot.actionDeleteMarker.isChecked():
2256  self.dbplot.on_actionDeleteMarker_toggled(False)
2257  return True
2258 
2259  if self.dbplot.actionMoveMarker.isChecked():
2260  if self.dbplot.draggingMode: self.dbplot.deselectMarker()
2261  else: self.dbplot.on_actionMoveMarker_toggled(False)
2262  return True
2263  return True
2264 
2265  if event.button() == Qt.LeftButton:
2266 
2267  if not self.dbplot.draggingMode: # Do not process LeftButton clicks when dragging mouse.
2268  if self.dbplot.actionNewMarker.isChecked(): self.dbplot.addMarker(event.pos())
2269  elif self.dbplot.actionDeleteMarker.isChecked(): self.dbplot.deleteMarker(event.pos())
2270  elif self.dbplot.actionMoveMarker.isChecked(): self.dbplot.selectAndHighlightMarker(event.pos())
2271  # Do not return True in order to allow QwtPlotPicker to process the event
2272 
2273 # elif event.type() == QEvent.MouseButtonRelease:
2274 # pass
2275 
2276  elif event.type() == QEvent.MouseMove:
2277  if self.dbplot.draggingMode and self.dbplot.markerMove is not None:
2278  self.dbplot.dragMarker(event.pos())
2279 
2280  elif event.type() == QEvent.KeyPress:
2281 
2282  if event.key() == Qt.Key_Delete:
2283  if self.dbplot.actionNewMarker.isChecked(): self.dbplot.deleteLastMarker()
2284  return True
2285 
2286  elif event.key() == Qt.Key_Escape:
2287  if self.dbplot.actionNewMarker.isChecked():
2288  self.dbplot.on_actionNewMarker_toggled(False)
2289  return True
2290  if self.dbplot.actionDeleteMarker.isChecked():
2291  self.dbplot.on_actionDeleteMarker_toggled(False)
2292  return True
2293  if self.dbplot.actionMoveMarker.isChecked():
2294  if self.dbplot.draggingMode: self.dbplot.deselectMarker()
2295  else: self.dbplot.on_actionMoveMarker_toggled(False)
2296  return True
2297 
2298  elif event.key() == Qt.Key_Right and self.dbplot.actionMoveMarker.isChecked():
2299  self.dbplot.shiftMarker(1)
2300  return True
2301 
2302  elif event.key() == Qt.Key_Left and self.dbplot.actionMoveMarker.isChecked():
2303  self.dbplot.shiftMarker(-1)
2304  return True
2305 
2306  return Qwt.QwtPlot.eventFilter(self, object, event)
2307 
2308 
2309 ##
2310 #
2311 # Class for markers.
2312 #
2313 class PlotMarker(Qwt.QwtPlotMarker):
2314 
2315  ##
2316  #
2317  # Class constructor. Gets the data from dbplot zoomers base in nTab.
2318  # @param dbplot = DbPlot class instance, to which this marker belongs.
2319  # @param Yaxis = y-axis to use Qwt.QwtPlot.yLeft or Qwt.QwtPlot.yRight.
2320  # @param nTab = Tab widget index.
2321  # @param curve = QwtPlotCurve instance, to which this marker is linked.
2322  # @param index = Index of curve point.
2323  #
2324  def __init__(self, dbplot, axis, nTab, curve, index):
2325 
2326  ##
2327  # DbPlot class instance, to which this marker belongs.
2328  self.dbplot = dbplot
2329 
2330  ##
2331  # y-axis to use Qwt.QwtPlot.yLeft or Qwt.QwtPlot.yRight.
2332  self.axis = axis
2333 
2334  ##
2335  # Tab widget index
2336  self.nTab = nTab
2337 
2338  ##
2339  # QwtPlotCurve instance, to which this marker is linked
2340  self.curve = curve
2341 
2342  ##
2343  # Index of curve x-data closest to x
2344  self.index = index
2345 
2346  ##
2347  # Marker x-coordinate
2348  self.x = curve.x(index)
2349 
2350  ##
2351  # Marker y-coordinate
2352  self.y = curve.y(index)
2353 
2354  ##
2355  # QwtPlot class instance where this marker is attached to.
2356  self.hPlot = dbplot.hPlot[nTab]
2357 
2358  super(PlotMarker, self).__init__()
2359  self.setYAxis(axis)
2360  self.setValue(self.x, self.y)
2361  self.updateLabel()
2362 
2363  ##
2364  # QwtSymbol class instance to use when the marker state is normal
2365  self.symbolNormal = Qwt.QwtSymbol(Qwt.QwtSymbol.Cross,QBrush(Qt.black), QPen(Qt.black, 2), QSize(10, 10) )
2366 
2367  ##
2368  # QwtSymbol class instance to use when the marker state is selected for move
2369  self.symbolMove = Qwt.QwtSymbol(Qwt.QwtSymbol.Cross,QBrush(Qt.red), QPen(Qt.red, 3), QSize(15, 15) )
2370 
2371  ##
2372  # QwtSymbol class instance to use when the marker state is selected for deletion
2373  self.symbolDel = Qwt.QwtSymbol(Qwt.QwtSymbol.UTriangle,QBrush(Qt.yellow), QPen(Qt.red, 2), QSize(10, 10) )
2374 
2375  self.setSymbol(self.symbolNormal)
2376 
2377 
2378  ##
2379  #
2380  # Update marker label according to required precision.
2381  #
2382  def updateLabel(self):
2383  if self.axis == Qwt.QwtPlot.yLeft:
2384  units = self.dbplot.leftYunits[self.nTab]
2385  elif self.axis == Qwt.QwtPlot.yRight:
2386  units = self.dbplot.rightYunits[self.nTab]
2387 
2388  text = Qwt.QwtText( "%.*g%s, %.*g%s" % (self.dbplot.precX[self.nTab], self.x, self.dbplot.Xunits[self.nTab], self.dbplot.precY[self.nTab], self.y, units ) )
2389 # fn = self.fontInfo().family()
2390 # text.setFont(QFont(fn, 12, QFont.Bold))
2391  self.setLabel(text)
2392  self.setLabelAlignment(Qt.AlignRight | Qt.AlignBottom)
2393 
2394 
2395  ##
2396  #
2397  # Update marker position after a curve update with DbPlot.update().
2398  #
2399  def updatePosition(self):
2400  self.x = self.curve.x(self.index)
2401  self.y = self.curve.y(self.index)
2402  self.setValue(self.x, self.y)
2403  self.updateLabel()
2404 
2405 
2406 ##
2407 #
2408 # Class that stores zoomer base.
2409 # It is useul to autoscale to the zoomer base, not using the method setAxisAutoScale, which would scale accounting the masks.
2410 #
2411 class AutoScaleZoomerBase(object):
2412 
2413  ##
2414  #
2415  # Class constructor. Gets the data from dbplot zoomers base in nTab.
2416  # @param leftZoomerBaseRect = Left zoomer base rectangle (QRectF).
2417  # @param rightZoomerBaseRect = Right zoomer base rectangle (QRectF).
2418  #
2419  def __init__(self, leftZoomerBaseRect, rightZoomerBaseRect):
2420 
2421  ##
2422  # Left zoomer base.
2423  self.leftZoomerBaseRect = leftZoomerBaseRect
2424 
2425  ##
2426  # Right zoomer base for autoscale,.
2427  self.rightZoomerBaseRect = rightZoomerBaseRect
2428 
2429  ##
2430  # Zoomer base for autoscale, Xmin.
2431  self.Xmin = self.leftZoomerBaseRect.x()
2432 
2433  ##
2434  # Zoomer base for autoscale, Xmax.
2435  self.Xmax = self.leftZoomerBaseRect.x() + self.leftZoomerBaseRect.width()
2436 
2437  ##
2438  # Zoomer base for autoscale, LeftYmin.
2439  self.leftYmin = self.leftZoomerBaseRect.y()
2440 
2441  ##
2442  # Zoomer base for autoscale, LeftYmax.
2443  self.leftYmax = self.leftZoomerBaseRect.y() + self.leftZoomerBaseRect.height()
2444 
2445  ##
2446  # Zoomer base for autoscale, RightYmin.
2447  self.rightYmin = self.rightZoomerBaseRect.y()
2448 
2449  ##
2450  # Zoomer base for autoscale, RightYmax.
2451  self.rightYmax = self.rightZoomerBaseRect.y() + self.rightZoomerBaseRect.height()
2452 
2453 
2454  ##
2455  #
2456  # Given a set of max and min values for all axis, computes and returns the zoomer base rectangles.
2457  # The attributes of AutoScaleZoomerBase class instance are updated accordingly.
2458  # @return leftZoomerBaseRect = Left zoomer base rectangle (QRectF).
2459  # @return rightZoomerBaseRect = Right zoomer base rectangle (QRectF).
2460  #
2461  def updateZoomerBase(self, Xmin, Xmax, leftYmin, leftYmax, rightYmin, rightYmax):
2462 
2463  self.Xmin = Xmin
2464  self.Xmax = Xmax
2465  self.leftYmin = leftYmin
2466  self.leftYmax = leftYmax
2467  self.rightYmin = rightYmin
2468  self.rightYmax = rightYmax
2469  self.leftZoomerBaseRect = QRectF(Xmin, leftYmin, Xmax - Xmin, leftYmax - leftYmin ) if max(np.abs( [Xmin, leftYmin, Xmax, leftYmax] )) < np.inf else None
2470  self.rightZoomerBaseRect = QRectF(Xmin, rightYmin, Xmax - Xmin, rightYmax - rightYmin ) if max(np.abs( [Xmin, rightYmin, Xmax, rightYmax] )) < np.inf else None
2471 
2472  return self.leftZoomerBaseRect, self.rightZoomerBaseRect
2473 
2474 
2475 ##
2476 #
2477 # Filter to transform color of some plot items and fontsize when printing.
2478 #
2479 class PrintFilter(Qwt.QwtPlotPrintFilter):
2480  ##
2481  #
2482  # Constructor. The class is derived from Qwt5.QwtPlotPrintFilter.
2483  #
2484  def __init__(self):
2485  Qwt.QwtPlotPrintFilter.__init__(self)
2486 
2487  ##
2488  #
2489  # Modifies a color for printing.
2490  # @param c = Color to be modified (QColor).
2491  # @param item = Type of item where the color belongs.
2492  # @return Modified color (QColor).
2493  #
2494  def color(self, c, item):
2495  if not (self.options() & Qwt.QwtPlotPrintFilter.CanvasBackground):
2496  if item == Qwt.QwtPlotPrintFilter.MajorGrid:
2497  return Qt.darkGray
2498  elif item == Qwt.QwtPlotPrintFilter.MinorGrid:
2499  return Qt.gray
2500  if item == Qwt.QwtPlotPrintFilter.CanvasBackground:
2501  return Qt.white
2502 # elif item == Qwt.QwtPlotPrintFilter.Title:
2503 # return Qt.red
2504 # elif item == Qwt.QwtPlotPrintFilter.AxisScale:
2505 # return Qt.green
2506 # elif item == Qwt.QwtPlotPrintFilter.AxisTitle:
2507 # return Qt.blue
2508  return c
2509 
2510  ##
2511  #
2512  # Modifies a font for printing.
2513  # @param f = Font to be modified (QFont).
2514  # @param item = Type of item where the font belongs.
2515  # @return All fonts are returned unmodified.
2516  #
2517  def font(self, f, item):
2518  result = QFont(f)
2519 # result.setPointSize(int(f.pointSize()*1.25))
2520  return result
2521 
2522 
2523 # Main program for testing purposes #
2524 
2525 if __name__ == '__main__':
2526  # QApplication class instance, containing our application
2527  app = QApplication(sys.argv)
2528  ui = DbPlot( [0, 1, 2, 3], [101, 52, 803, 500], XLabel = 'y-axis' , leftYLabel = 'Left axis', leftYNames = ['Left-axis data'],
2529  rightYData = [0.3, -0.5, 0.2, 0.4], rightYLabel = 'Right axis', rightYNames = ['Right-axis data'], rightYColors = [ Qt.red],
2530  Xunits = 'GHz', leftYunits = 'dBm',
2531  windowTitle = 'Example', tabTitle = 'Single-curve', title = '1 curve (list)', textLabel = 'A two-line\ntext label')
2532 
2533  N = 100
2534  x = np.arange(np.pi/N, np.pi, np.pi / N )
2535  ly1 = 10*np.log10(np.sin(x))
2536  ly2 = 10*np.log10(np.abs(np.cos(x)))
2537  ly2[ly2 < -10] = -10
2538  ry1 = 10*np.log10(np.sinh(x))
2539  ry2 = 10*np.log10(np.cosh(x))
2540  ui.addTab( 180*x/np.pi, [ ly1, ly2], XLabel = 'x' , leftYNames = ['Sin(x)', '|Cos(x)|'], leftYLabel = 'Trigonometric values (dB)',
2541  rightYData = [ ry1, ry2 ], rightYLabel = 'Hyperbolic values (dB)', rightYNames = ['Sinh(x)', 'Cosh(x)'], rightYColors = [ Qt.cyan, Qt.green],
2542  Xunits = 'rad', leftYunits = 'dB', leftWidth = [2, 2], rightWidth = [2, 2],
2543  tabTitle = 'Multi-curve', title = '2 curves (numpy arrays) with masks', leftYVisible = [True, False], rightYVisible = [False, True])
2544 
2545  mask = np.array( [ (20, -4), (60, -2), (85, -0.5), (90, -0.4), (95, -0.5), (120, -2), (160, -4) ] )
2546  ui.addMask(nTab = 1, XData = mask[:, 0], YData = mask[:, 1], axis = 'left', name = 'Lower Mask', color = Qt.blue, type = 'Lower', transpar = 32 )
2547 
2548  mask = np.array( [ (40, -2), (80, -5), (80, np.nan), (100, np.nan), (100, -5), (140, -2) ] )
2549  ui.addMask(nTab = 1, XData = mask[:, 0], YData = mask[:, 1], axis = 'left', name = 'Upper Mask', color = Qt.red, type = 'Upper', transpar = 32)
2550 
2551  mask = np.array([ (50, 0), (100, 4), (150, 8) ])
2552  ui.addMask(nTab = 1, XData = mask[:, 0], YData = mask[:, 1], axis = 'right', name = 'Right lower Mask', color = Qt.green, type = 'Lower', transpar = 64)
2553 
2554 # ui.changeCurveVisibility( nTab = 1, leftYVisible = [True, False], rightYVisible = None, masksVisible = [None, True, False, ] )
2555 
2556  ui.addTab( XData = [[0, 1, 2, 3], [0.5, 1.5, 2.5, 3.5, 4.5]], leftYData = [ [20, 10, 1, 0], [3, 5, -2, 4, 1] ], leftYNames = ['width=2', 'width=3'], leftYLabel = 'Left axis',
2557  rightXData = [2.5, 3.5, 4.5, 5.5], rightYData = [ 25, 35, 45, 55], rightYNames = 'width=4', rightYLabel = 'Right-y axis',
2558  leftWidth = [2, 3], rightWidth = 4,
2559  tabTitle = 'Multi-axis', title = 'Example with different x-axis data for each curve, and manual scaling', Xmin = 1, Xmax = 5, leftYmin = 0, rightYmax = 50)
2560 
2561  ui.addTab( XData = np.array([0, 1, 2, 3]), leftYData = [ np.array([5, 10, 1, 0]), np.array([[6, 9, 3, 2], [6.2, 9.2, 3.2, 2.2], [6.4, 9.4, 3.4, 2.4]]).T ], leftYNames = ['1-D array Left', '2-D array Left' ], leftYLabel = 'Left axis',
2562  rightXData = np.array([0.5, 1.5, 2.5, 3.5]), rightYData = np.array([[ 25, 5, 15, 35], [25.4, 5.4, 15.4, 35.4], [25.8, 5.8, 15.8, 35.8]]).T, rightYNames = '2-D array Right', rightYLabel = 'Right-y axis',
2563  tabTitle = 'Numpy arrays', title = 'Example with data in 2-D numpy arrays')
2564 
2565  ui.addTab( XData = np.array([0, 1, 2, 3]), leftYData = np.array([[6, 9, 3, 2], [6.2, 9.2, 3.2, 2.2], [6.4, 9.4, 3.4, 2.4]]).T, leftYNames = '2-D array Left' , leftYLabel = 'Left axis',
2566  tabTitle = 'Left-Y only', title = 'Example with data in left-Y axis only')
2567 
2568  ui.addTab( XData = [0, 1], leftYData = [ [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9] ],
2569  tabTitle = 'Multi-color', title = 'Example with many curves of different colors',
2570  leftYVisible = [True, True, True, True, True, True, True, True ])
2571 
2572  ui.update(5, XData = [0, 10], leftYData = [ [10.35, 20], [20, 30], [30, 40], [40, 50], [50, 60], [60, 70], [70, 80], [80, 90.14] ])
2573  ui.autoScaleSomeAxis(5, autoScaleBottomX = True, autoScaleLeftY = True, autoScaleRightY = False)
2574 
2575  ui.addTab( 1.1*180*x/np.pi, [ 1.1*ly1, 1.1*ly2], XLabel = 'x' , leftYNames = ['Sin(x) 1.1', '|Cos(x)| 1.1'], leftYLabel = 'Trigonometric values (dB) 1.1',
2576  rightYData = [ 1.1*ry1, 1.1*ry2 ], rightYLabel = 'Hyperbolic values (dB) 1.1', rightYNames = ['Sinh(x) 1.1', 'Cosh(x) 1.1'], rightYColors = [ Qt.cyan, Qt.green],
2577  Xunits = 'rad 1.1', leftYunits = 'dB 1.1', leftWidth = [2, 2], rightWidth = [2, 2],
2578  tabTitle = 'Multi-curve 1.1', title = '2 curves (numpy arrays) with masks 1.1', leftYVisible = [True, False], rightYVisible = [False, True],
2579  replaceTab = 1)
2580 
2581  ui.addTab( XData = [[0, 10, 20, 30, 40], [5, 15, 25, 35, 45, 55]], leftYData = [ [200, 100, 10, 0, 50], [30, 50, -20, 40, 10, -30] ], leftYNames = ['width10=2', 'width10=3'], leftYLabel = 'Left axis 10',
2582  rightXData = [25, 35, 45, 55, 65], rightYData = [ 2500, 3500, 4500, 5500, 4500], rightYNames = 'width10=4', rightYLabel = 'Right-y axis 10',
2583  leftWidth = [2, 3], rightWidth = 4,
2584  tabTitle = 'Multi-axis 10', title = 'Example of replaceTab', Xmin = 10, Xmax = 60, leftYmin = 0, rightYmax = 5000,
2585  replaceTab = 2)
2586 
2587  ui.addTab( XData = 10*np.array([0, 1, 2, 3]), leftYData = [ 10*np.array([5, 10, 1, 0]), 10*np.array([[6, 9, 3, 2], [6.2, 9.2, 3.2, 2.2], [6.4, 9.4, 3.4, 2.4]]).T ], leftYNames = ['1-D array Left 10', '2-D array Left 10' ], leftYLabel = 'Left axis 10',
2588  rightXData = 10*np.array([0.5, 1.5, 2.5, 3.5]), rightYData = 10*np.array([[ 25, 5, 15, 35], [25.4, 5.4, 15.4, 35.4], [25.8, 5.8, 15.8, 35.8]]).T, rightYNames = '2-D array Right 10', rightYLabel = 'Right-y axis 10',
2589  tabTitle = 'Numpy arrays 10', title = 'Example of replace Tab with 2-D numpy arrays',
2590  replaceTab = 3)
2591 
2592  app.exec_()
2593 
2594