eric6/MicroPython/MicroPythonGraphWidget.py

branch
micropython
changeset 7066
e3d034e65afc
parent 7065
e3d04faced34
child 7067
3fc4082fc6ba
--- a/eric6/MicroPython/MicroPythonGraphWidget.py	Tue Jul 16 20:12:53 2019 +0200
+++ b/eric6/MicroPython/MicroPythonGraphWidget.py	Wed Jul 17 20:31:47 2019 +0200
@@ -11,15 +11,22 @@
 
 from collections import deque
 import bisect
+import os
+import time
+import csv
 
-from PyQt5.QtCore import pyqtSignal, pyqtSlot
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
 from PyQt5.QtGui import QPainter
 from PyQt5.QtWidgets import (
-    QWidget, QHBoxLayout, QVBoxLayout, QToolButton, QSizePolicy, QSpacerItem
+    QWidget, QHBoxLayout, QVBoxLayout, QToolButton, QSizePolicy, QSpacerItem,
+    QLabel, QSpinBox
 )
 from PyQt5.QtChart import QChartView, QChart, QLineSeries, QValueAxis
 
+from E5Gui import E5MessageBox
+
 import UI.PixmapCache
+import Preferences
 
 
 class MicroPythonGraphWidget(QWidget):
@@ -40,7 +47,7 @@
         super(MicroPythonGraphWidget, self).__init__(parent)
         
         self.__layout = QHBoxLayout()
-        self.__layout.setContentsMargins(0, 0, 0, 0)
+        self.__layout.setContentsMargins(2, 2, 2, 2)
         self.setLayout(self.__layout)
         
         self.__chartView = QChartView(self)
@@ -48,26 +55,42 @@
             QSizePolicy.Expanding, QSizePolicy.Expanding)
         self.__layout.addWidget(self.__chartView)
         
-        self.__buttonsLayout = QVBoxLayout()
-        self.__buttonsLayout.setContentsMargins(0, 0, 0, 0)
-        self.__layout.addLayout(self.__buttonsLayout)
+        self.__verticalLayout = QVBoxLayout()
+        self.__verticalLayout.setContentsMargins(0, 0, 0, 0)
+        self.__layout.addLayout(self.__verticalLayout)
         
         self.__saveButton = QToolButton(self)
         self.__saveButton.setIcon(UI.PixmapCache.getIcon("fileSave"))
         self.__saveButton.setToolTip(self.tr("Press to save the raw data"))
         self.__saveButton.clicked.connect(self.on_saveButton_clicked)
-        self.__buttonsLayout.addWidget(self.__saveButton)
+        self.__verticalLayout.addWidget(self.__saveButton)
+        self.__verticalLayout.setAlignment(self.__saveButton, Qt.AlignHCenter)
         
         spacerItem = QSpacerItem(20, 20, QSizePolicy.Minimum,
                                  QSizePolicy.Expanding)
-        self.__buttonsLayout.addItem(spacerItem)
+        self.__verticalLayout.addItem(spacerItem)
+        
+        label = QLabel(self.tr("max. X:"))
+        self.__verticalLayout.addWidget(label)
+        self.__verticalLayout.setAlignment(label, Qt.AlignHCenter)
+        
+        self.__maxX = 100
+        self.__maxXSpinBox = QSpinBox()
+        self.__maxXSpinBox.setMinimum(100)
+        self.__maxXSpinBox.setMaximum(1000)
+        self.__maxXSpinBox.setSingleStep(100)
+        self.__maxXSpinBox.setToolTip(self.tr(
+            "Enter the maximum number of data points to be plotted."))
+        self.__maxXSpinBox.setValue(self.__maxX)
+        self.__maxXSpinBox.setAlignment(Qt.AlignRight)
+        self.__verticalLayout.addWidget(self.__maxXSpinBox)
         
         # holds the data to be checked for plotable data
         self.__inputBuffer = []
         # holds the raw data
         self.__rawData = []
+        self.__dirty = False
         
-        self.__maxX = 100
         self.__maxY = 1000
         self.__flooded = False  # flag indicating a data flood
         
@@ -91,6 +114,8 @@
         self.__chart.setAxisY(self.__axisY, self.__series[0])
         self.__chartView.setChart(self.__chart)
         self.__chartView.setRenderHint(QPainter.Antialiasing)
+        
+        self.__maxXSpinBox.valueChanged.connect(self.__handleMaxXChanged)
     
     @pyqtSlot(bytes)
     def processData(self, data):
@@ -115,6 +140,10 @@
             self.dataFlood.emit()
             return
         
+        # disable the inputs while processing data
+        self.__saveButton.setEnabled(False)
+        self.__maxXSpinBox.setEnabled(False)
+        
         data = data.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
         self.__inputBuffer.append(data)
         
@@ -153,6 +182,10 @@
             # Append any left over bytes for processing next time data is
             # received.
             self.__inputBuffer.append(lines[-1])
+        
+        # re-enable the inputs
+        self.__saveButton.setEnabled(True)
+        self.__maxXSpinBox.setEnabled(True)
     
     def __addData(self, values):
         """
@@ -167,6 +200,7 @@
         """
         # store incoming data to be able to dump it as CSV upon request
         self.__rawData.append(values)
+        self.__dirty = True
         
         # check number of incoming values and adjust line series accordingly
         if len(values) != len(self.__series):
@@ -238,7 +272,16 @@
         @return flag indicating valid data
         @rtype bool
         """
-        # TODO: not implemented yet
+        return len(self.__rawData) > 0
+    
+    def isDirty(self):
+        """
+        Public method to check, if the chart contains unsaved data.
+        
+        @return flag indicating unsaved data
+        @rtype bool
+        """
+        return self.hasData() and self.__dirty
     
     def saveData(self):
         """
@@ -247,4 +290,56 @@
         @return flag indicating success
         @rtype bool
         """
-        # TODO: not implemented yet
+        baseDir = (Preferences.getMultiProject("Workspace") or
+                   os.path.expanduser("~"))
+        dataDir = os.path.join(baseDir, "data_capture")
+        
+        if not os.path.exists(dataDir):
+            os.makedirs(dataDir)
+        
+        # save the raw data as a CSV file
+        fileName = "{0}.csv".format(time.strftime("%Y%m%d-%H%M%S"))
+        fullPath = os.path.join(dataDir, fileName)
+        try:
+            csvFile = open(fullPath, "w")
+            csvWriter = csv.writer(csvFile)
+            csvWriter.writerows(self.__rawData)
+            csvFile.close()
+            
+            self.__dirty = False
+            return True
+        except (IOError, OSError) as err:
+            E5MessageBox.critical(
+                self,
+                self.tr("Save Chart Data"),
+                self.tr(
+                    """<p>The chart data could not be saved into file"""
+                    """ <b>{0}</b>.</p><p>Reason: {1}</p>""").format(
+                    fullPath, str(err)))
+            return False
+    
+    @pyqtSlot(int)
+    def __handleMaxXChanged(self, value):
+        """
+        Private slot handling a change of the max. X spin box.
+        
+        @param value value of the spin box
+        @type int
+        """
+        delta = value - self.__maxX
+        if delta == 0:
+            # nothing to change
+            return
+        elif delta > 0:
+            # range must be increased
+            for deq in self.__data:
+                deq.extend([0] * delta)
+        else:
+            # range must be decreased
+            data = []
+            for deq in self.__data:
+                data.append(deque(list(deq)[:value]))
+            self.__data = data
+        
+        self.__maxX = value
+        self.__axisX.setRange(0, self.__maxX)

eric ide

mercurial