eric6/MicroPython/MicroPythonGraphWidget.py

branch
micropython
changeset 7066
e3d034e65afc
parent 7065
e3d04faced34
child 7067
3fc4082fc6ba
equal deleted inserted replaced
7065:e3d04faced34 7066:e3d034e65afc
9 9
10 from __future__ import unicode_literals 10 from __future__ import unicode_literals
11 11
12 from collections import deque 12 from collections import deque
13 import bisect 13 import bisect
14 14 import os
15 from PyQt5.QtCore import pyqtSignal, pyqtSlot 15 import time
16 import csv
17
18 from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
16 from PyQt5.QtGui import QPainter 19 from PyQt5.QtGui import QPainter
17 from PyQt5.QtWidgets import ( 20 from PyQt5.QtWidgets import (
18 QWidget, QHBoxLayout, QVBoxLayout, QToolButton, QSizePolicy, QSpacerItem 21 QWidget, QHBoxLayout, QVBoxLayout, QToolButton, QSizePolicy, QSpacerItem,
22 QLabel, QSpinBox
19 ) 23 )
20 from PyQt5.QtChart import QChartView, QChart, QLineSeries, QValueAxis 24 from PyQt5.QtChart import QChartView, QChart, QLineSeries, QValueAxis
21 25
26 from E5Gui import E5MessageBox
27
22 import UI.PixmapCache 28 import UI.PixmapCache
29 import Preferences
23 30
24 31
25 class MicroPythonGraphWidget(QWidget): 32 class MicroPythonGraphWidget(QWidget):
26 """ 33 """
27 Class implementing the MicroPython graph widget. 34 Class implementing the MicroPython graph widget.
38 @type QWidget 45 @type QWidget
39 """ 46 """
40 super(MicroPythonGraphWidget, self).__init__(parent) 47 super(MicroPythonGraphWidget, self).__init__(parent)
41 48
42 self.__layout = QHBoxLayout() 49 self.__layout = QHBoxLayout()
43 self.__layout.setContentsMargins(0, 0, 0, 0) 50 self.__layout.setContentsMargins(2, 2, 2, 2)
44 self.setLayout(self.__layout) 51 self.setLayout(self.__layout)
45 52
46 self.__chartView = QChartView(self) 53 self.__chartView = QChartView(self)
47 self.__chartView.setSizePolicy( 54 self.__chartView.setSizePolicy(
48 QSizePolicy.Expanding, QSizePolicy.Expanding) 55 QSizePolicy.Expanding, QSizePolicy.Expanding)
49 self.__layout.addWidget(self.__chartView) 56 self.__layout.addWidget(self.__chartView)
50 57
51 self.__buttonsLayout = QVBoxLayout() 58 self.__verticalLayout = QVBoxLayout()
52 self.__buttonsLayout.setContentsMargins(0, 0, 0, 0) 59 self.__verticalLayout.setContentsMargins(0, 0, 0, 0)
53 self.__layout.addLayout(self.__buttonsLayout) 60 self.__layout.addLayout(self.__verticalLayout)
54 61
55 self.__saveButton = QToolButton(self) 62 self.__saveButton = QToolButton(self)
56 self.__saveButton.setIcon(UI.PixmapCache.getIcon("fileSave")) 63 self.__saveButton.setIcon(UI.PixmapCache.getIcon("fileSave"))
57 self.__saveButton.setToolTip(self.tr("Press to save the raw data")) 64 self.__saveButton.setToolTip(self.tr("Press to save the raw data"))
58 self.__saveButton.clicked.connect(self.on_saveButton_clicked) 65 self.__saveButton.clicked.connect(self.on_saveButton_clicked)
59 self.__buttonsLayout.addWidget(self.__saveButton) 66 self.__verticalLayout.addWidget(self.__saveButton)
67 self.__verticalLayout.setAlignment(self.__saveButton, Qt.AlignHCenter)
60 68
61 spacerItem = QSpacerItem(20, 20, QSizePolicy.Minimum, 69 spacerItem = QSpacerItem(20, 20, QSizePolicy.Minimum,
62 QSizePolicy.Expanding) 70 QSizePolicy.Expanding)
63 self.__buttonsLayout.addItem(spacerItem) 71 self.__verticalLayout.addItem(spacerItem)
72
73 label = QLabel(self.tr("max. X:"))
74 self.__verticalLayout.addWidget(label)
75 self.__verticalLayout.setAlignment(label, Qt.AlignHCenter)
76
77 self.__maxX = 100
78 self.__maxXSpinBox = QSpinBox()
79 self.__maxXSpinBox.setMinimum(100)
80 self.__maxXSpinBox.setMaximum(1000)
81 self.__maxXSpinBox.setSingleStep(100)
82 self.__maxXSpinBox.setToolTip(self.tr(
83 "Enter the maximum number of data points to be plotted."))
84 self.__maxXSpinBox.setValue(self.__maxX)
85 self.__maxXSpinBox.setAlignment(Qt.AlignRight)
86 self.__verticalLayout.addWidget(self.__maxXSpinBox)
64 87
65 # holds the data to be checked for plotable data 88 # holds the data to be checked for plotable data
66 self.__inputBuffer = [] 89 self.__inputBuffer = []
67 # holds the raw data 90 # holds the raw data
68 self.__rawData = [] 91 self.__rawData = []
69 92 self.__dirty = False
70 self.__maxX = 100 93
71 self.__maxY = 1000 94 self.__maxY = 1000
72 self.__flooded = False # flag indicating a data flood 95 self.__flooded = False # flag indicating a data flood
73 96
74 self.__data = [deque([0] * self.__maxX)] 97 self.__data = [deque([0] * self.__maxX)]
75 self.__series = [QLineSeries()] 98 self.__series = [QLineSeries()]
89 self.__axisY.setLabelFormat("%d") 112 self.__axisY.setLabelFormat("%d")
90 self.__chart.setAxisX(self.__axisX, self.__series[0]) 113 self.__chart.setAxisX(self.__axisX, self.__series[0])
91 self.__chart.setAxisY(self.__axisY, self.__series[0]) 114 self.__chart.setAxisY(self.__axisY, self.__series[0])
92 self.__chartView.setChart(self.__chart) 115 self.__chartView.setChart(self.__chart)
93 self.__chartView.setRenderHint(QPainter.Antialiasing) 116 self.__chartView.setRenderHint(QPainter.Antialiasing)
117
118 self.__maxXSpinBox.valueChanged.connect(self.__handleMaxXChanged)
94 119
95 @pyqtSlot(bytes) 120 @pyqtSlot(bytes)
96 def processData(self, data): 121 def processData(self, data):
97 """ 122 """
98 Public slot to process the raw data. 123 Public slot to process the raw data.
112 137
113 if len(data) > 1024: 138 if len(data) > 1024:
114 self.__flooded = True 139 self.__flooded = True
115 self.dataFlood.emit() 140 self.dataFlood.emit()
116 return 141 return
142
143 # disable the inputs while processing data
144 self.__saveButton.setEnabled(False)
145 self.__maxXSpinBox.setEnabled(False)
117 146
118 data = data.replace(b"\r\n", b"\n").replace(b"\r", b"\n") 147 data = data.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
119 self.__inputBuffer.append(data) 148 self.__inputBuffer.append(data)
120 149
121 # check if the data contains a Python tuple containing numbers (int 150 # check if the data contains a Python tuple containing numbers (int
151 self.__inputBuffer = [] 180 self.__inputBuffer = []
152 if lines[-1] and not lines[-1].endswith(b"\n"): 181 if lines[-1] and not lines[-1].endswith(b"\n"):
153 # Append any left over bytes for processing next time data is 182 # Append any left over bytes for processing next time data is
154 # received. 183 # received.
155 self.__inputBuffer.append(lines[-1]) 184 self.__inputBuffer.append(lines[-1])
185
186 # re-enable the inputs
187 self.__saveButton.setEnabled(True)
188 self.__maxXSpinBox.setEnabled(True)
156 189
157 def __addData(self, values): 190 def __addData(self, values):
158 """ 191 """
159 Private method to add a tuple of values to the graph. 192 Private method to add a tuple of values to the graph.
160 193
165 @param values tuple containing the data to be added 198 @param values tuple containing the data to be added
166 @type tuple of int or float 199 @type tuple of int or float
167 """ 200 """
168 # store incoming data to be able to dump it as CSV upon request 201 # store incoming data to be able to dump it as CSV upon request
169 self.__rawData.append(values) 202 self.__rawData.append(values)
203 self.__dirty = True
170 204
171 # check number of incoming values and adjust line series accordingly 205 # check number of incoming values and adjust line series accordingly
172 if len(values) != len(self.__series): 206 if len(values) != len(self.__series):
173 valuesLen = len(values) 207 valuesLen = len(values)
174 seriesLen = len(self.__series) 208 seriesLen = len(self.__series)
236 Public method to check, if the chart contains some valid data. 270 Public method to check, if the chart contains some valid data.
237 271
238 @return flag indicating valid data 272 @return flag indicating valid data
239 @rtype bool 273 @rtype bool
240 """ 274 """
241 # TODO: not implemented yet 275 return len(self.__rawData) > 0
276
277 def isDirty(self):
278 """
279 Public method to check, if the chart contains unsaved data.
280
281 @return flag indicating unsaved data
282 @rtype bool
283 """
284 return self.hasData() and self.__dirty
242 285
243 def saveData(self): 286 def saveData(self):
244 """ 287 """
245 Public method to save the dialog's raw data. 288 Public method to save the dialog's raw data.
246 289
247 @return flag indicating success 290 @return flag indicating success
248 @rtype bool 291 @rtype bool
249 """ 292 """
250 # TODO: not implemented yet 293 baseDir = (Preferences.getMultiProject("Workspace") or
294 os.path.expanduser("~"))
295 dataDir = os.path.join(baseDir, "data_capture")
296
297 if not os.path.exists(dataDir):
298 os.makedirs(dataDir)
299
300 # save the raw data as a CSV file
301 fileName = "{0}.csv".format(time.strftime("%Y%m%d-%H%M%S"))
302 fullPath = os.path.join(dataDir, fileName)
303 try:
304 csvFile = open(fullPath, "w")
305 csvWriter = csv.writer(csvFile)
306 csvWriter.writerows(self.__rawData)
307 csvFile.close()
308
309 self.__dirty = False
310 return True
311 except (IOError, OSError) as err:
312 E5MessageBox.critical(
313 self,
314 self.tr("Save Chart Data"),
315 self.tr(
316 """<p>The chart data could not be saved into file"""
317 """ <b>{0}</b>.</p><p>Reason: {1}</p>""").format(
318 fullPath, str(err)))
319 return False
320
321 @pyqtSlot(int)
322 def __handleMaxXChanged(self, value):
323 """
324 Private slot handling a change of the max. X spin box.
325
326 @param value value of the spin box
327 @type int
328 """
329 delta = value - self.__maxX
330 if delta == 0:
331 # nothing to change
332 return
333 elif delta > 0:
334 # range must be increased
335 for deq in self.__data:
336 deq.extend([0] * delta)
337 else:
338 # range must be decreased
339 data = []
340 for deq in self.__data:
341 data.append(deque(list(deq)[:value]))
342 self.__data = data
343
344 self.__maxX = value
345 self.__axisX.setRange(0, self.__maxX)

eric ide

mercurial