Microwave filters GUI 2.0.3

- Main Page
- Related Pages
- Packages
- Classes
- Files

- File List

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)
585 self.on_actionDeleteMarker_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().
704 self.on_tabWidget_currentChanged(0)
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 #
1297 def updateMoveKnobRange(self):
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 #
1572 def on_tabWidget_currentChanged(self, index):
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