]> sigrok.org Git - sigrok-meter.git/commitdiff
Make it possible to hide plots.
authorJens Steinhauser <redacted>
Sun, 4 Oct 2015 03:20:44 +0000 (05:20 +0200)
committerJens Steinhauser <redacted>
Sun, 4 Oct 2015 13:09:07 +0000 (15:09 +0200)
datamodel.py
mainwindow.py
multiplotwidget.py

index 52884eb6de29e4f049a633234e28e68a720e6d8d..53c70bc46bc635c3f47102b13c6598bdbfcf7b9e 100644 (file)
@@ -33,6 +33,17 @@ except ImportError:
 QtCore = qtcompat.QtCore
 QtGui = qtcompat.QtGui
 
+class Trace(object):
+    '''Class to hold the measured samples.'''
+
+    def __init__(self):
+        self.samples = []
+        self.new = False
+
+    def append(self, sample):
+        self.samples.append(sample)
+        self.new = True
+
 class MeasurementDataModel(QtGui.QStandardItemModel):
     '''Model to hold the measured values.'''
 
@@ -42,8 +53,8 @@ class MeasurementDataModel(QtGui.QStandardItemModel):
     '''Role used to store the device vendor and model.'''
     descRole = QtCore.Qt.UserRole + 2
 
-    '''Role used to store past samples.'''
-    samplesRole = QtCore.Qt.UserRole + 3
+    '''Role used to store a dictionary with the traces'''
+    tracesRole = QtCore.Qt.UserRole + 3
 
     '''Role used to store the color to draw the graph of the channel.'''
     colorRole = QtCore.Qt.UserRole + 4
@@ -126,7 +137,7 @@ class MeasurementDataModel(QtGui.QStandardItemModel):
         item = QtGui.QStandardItem()
         item.setData(uid, MeasurementDataModel.idRole)
         item.setData(desc, MeasurementDataModel.descRole)
-        item.setData(collections.defaultdict(list), MeasurementDataModel.samplesRole)
+        item.setData(collections.defaultdict(Trace), MeasurementDataModel.tracesRole)
         item.setData(next(self._colorgen), MeasurementDataModel.colorRole)
         self.appendRow(item)
         self.sort(0)
@@ -151,8 +162,8 @@ class MeasurementDataModel(QtGui.QStandardItemModel):
         # The samples role is a dictionary that contains the old samples for each unit.
         # Should be trimmed periodically, otherwise it grows larger and larger.
         sample = (time.time(), value)
-        d = item.data(MeasurementDataModel.samplesRole)
-        d[unit].append(sample)
+        traces = item.data(MeasurementDataModel.tracesRole)
+        traces[unit].append(sample)
 
 class MultimeterDelegate(QtGui.QStyledItemDelegate):
     '''Delegate to show the data items from a MeasurementDataModel.'''
index 19ce4d3ce79061cdcd0a0635f7d074d3c04249b7..8ac3b3de142f7fa828a0c7fdbf18a874b28c0097 100644 (file)
@@ -114,6 +114,7 @@ class MainWindow(QtGui.QMainWindow):
         self.listView.setMinimumSize(self.delegate.sizeHint())
 
         self.plotwidget = multiplotwidget.MultiPlotWidget(self)
+        self.plotwidget.plotHidden.connect(self._on_plotHidden)
 
         # Maps from 'unit' to the corresponding plot.
         self._plots = {}
@@ -174,33 +175,57 @@ class MainWindow(QtGui.QMainWindow):
     def timerEvent(self, event):
         '''Periodically updates all graphs.'''
 
-        for row in range(self.model.rowCount()):
-            idx = self.model.index(row, 0)
-            deviceID = self.model.data(idx, datamodel.MeasurementDataModel.idRole)
-            sampledict = self.model.data(idx, datamodel.MeasurementDataModel.samplesRole)
-            color = self.model.data(idx, datamodel.MeasurementDataModel.colorRole)
-            for unit in sampledict:
-                self._updatePlot(deviceID, unit, sampledict[unit], color)
-
-    def _updatePlot(self, deviceID, unit, samples, color):
-        '''Updates the curve of device 'deviceID' and 'unit' with 'samples',
-        changes the color of the curve to 'color'.'''
-
-        plot = self._getPlot(unit)
-        curve = self._getCurve(plot, deviceID)
-        curve.setPen(pyqtgraph.mkPen(color=color, width=1))
-
-        now = time.time()
+        self._updatePlots()
 
-        # remove old samples
-        l = now - MainWindow.BACKLOG
-        while samples and samples[0][0] < l:
-            samples.pop(0)
+    def _updatePlots(self):
+        '''Updates all plots.'''
 
-        xdata = [s[0] - now for s in samples]
-        ydata = [s[1]       for s in samples]
+        # loop over all devices and channels
+        for row in range(self.model.rowCount()):
+            idx = self.model.index(row, 0)
+            deviceID = self.model.data(idx,
+                            datamodel.MeasurementDataModel.idRole)
+            traces = self.model.data(idx,
+                            datamodel.MeasurementDataModel.tracesRole)
+
+            for unit, trace in traces.items():
+                now = time.time()
+
+                # remove old samples
+                l = now - MainWindow.BACKLOG
+                while trace.samples and trace.samples[0][0] < l:
+                    trace.samples.pop(0)
+
+                plot = self._getPlot(unit)
+                if not plot.visible:
+                    if trace.new:
+                        self.plotwidget.showPlot(plot)
+
+                if plot.visible:
+                    xdata = [s[0] - now for s in trace.samples]
+                    ydata = [s[1]       for s in trace.samples]
+
+                    color = self.model.data(idx,
+                                datamodel.MeasurementDataModel.colorRole)
+
+                    curve = self._getCurve(plot, deviceID)
+                    curve.setPen(pyqtgraph.mkPen(color=color))
+                    curve.setData(xdata, ydata)
+
+    @QtCore.Slot(multiplotwidget.Plot)
+    def _on_plotHidden(self, plot):
+        plotunit = [u for u, p in self._plots.items() if p == plot][0]
+
+        # Mark all traces of all devices/channels with the same unit as the
+        # plot as "old" ('trace.new = False'). As soon as a new sample arrives
+        # on one trace, the plot will be shown again
+        for row in range(self.model.rowCount()):
+            idx = self.model.index(row, 0)
+            traces = self.model.data(idx, datamodel.MeasurementDataModel.tracesRole)
 
-        curve.setData(xdata, ydata)
+            for traceunit, trace in traces.items():
+                if traceunit == plotunit:
+                    trace.new = False
 
     def closeEvent(self, event):
         self.thread.stop()
index 69e871a2414e47cf02995db99c0f9a8e03922af3..1d257bd450f3cfa63877a7195c454b44d84c928c 100755 (executable)
@@ -28,62 +28,136 @@ pyqtgraph = qtcompat.pyqtgraph
 pyqtgraph.setConfigOption('background', 'w')
 pyqtgraph.setConfigOption('foreground', 'k')
 
+class Plot(object):
+    '''Helper class to keep all graphics items of a plot together.'''
+
+    def __init__(self, view, xaxis, yaxis):
+        self.view = view
+        self.xaxis = xaxis
+        self.yaxis = yaxis
+        self.visible = False
+
 class MultiPlotItem(pyqtgraph.GraphicsWidget):
 
-    class Plot:
-        def __init__(self, view, xaxis, yaxis):
-            self.view = view
-            self.xaxis = xaxis
-            self.yaxis = yaxis
+    # Emitted when a plot is shown.
+    plotShown = QtCore.Signal()
+
+    # Emitted when a plot is hidden by the user via the context menu.
+    plotHidden = QtCore.Signal(Plot)
 
     def __init__(self, parent=None):
         pyqtgraph.GraphicsWidget.__init__(self, parent)
 
-        self.layout = QtGui.QGraphicsGridLayout()
-        self.layout.setContentsMargins(10, 10, 10, 1)
-        self.layout.setHorizontalSpacing(0)
-        self.layout.setVerticalSpacing(0)
-        self.setLayout(self.layout)
-
-        self._plots = []
+        self.setLayout(QtGui.QGraphicsGridLayout())
+        self.layout().setContentsMargins(10, 10, 10, 1)
+        self.layout().setHorizontalSpacing(0)
+        self.layout().setVerticalSpacing(0)
 
         for i in range(2):
-            self.layout.setColumnPreferredWidth(i, 0)
-            self.layout.setColumnMinimumWidth(i, 0)
-            self.layout.setColumnSpacing(i, 0)
+            self.layout().setColumnPreferredWidth(i, 0)
+            self.layout().setColumnMinimumWidth(i, 0)
+            self.layout().setColumnSpacing(i, 0)
+
+        self.layout().setColumnStretchFactor(0, 0)
+        self.layout().setColumnStretchFactor(1, 100)
+
+        # List of 'Plot' objects that are shown.
+        self._plots = []
 
-        self.layout.setColumnStretchFactor(0, 0)
-        self.layout.setColumnStretchFactor(1, 100)
+        self._hideActions = {}
 
     def addPlot(self):
-        row = self.layout.rowCount()
+        '''Adds and returns a new plot.'''
+
+        row = self.layout().rowCount()
 
         view = pyqtgraph.ViewBox(parent=self)
+
+        # If this is not the first plot, link to the axis of the previous one.
         if self._plots:
             view.setXLink(self._plots[-1].view)
-        self.layout.addItem(view, row, 1)
 
         yaxis = pyqtgraph.AxisItem(parent=self, orientation='left')
         yaxis.linkToView(view)
         yaxis.setGrid(255)
-        self.layout.addItem(yaxis, row, 0, QtCore.Qt.AlignRight)
 
         xaxis = pyqtgraph.AxisItem(parent=self, orientation='bottom')
         xaxis.linkToView(view)
         xaxis.setGrid(255)
-        self.layout.addItem(xaxis, row + 1, 1)
+
+        plot = Plot(view, xaxis, yaxis)
+        self._plots.append(plot)
+
+        self.showPlot(plot)
+
+        # Create a separate action object for each plots context menu, so that
+        # we can later find out which plot should be hidden by looking at
+        # 'self._hideActions'.
+        hideAction = QtGui.QAction('Hide', self)
+        hideAction.triggered.connect(self._onHideActionTriggered)
+        self._hideActions[id(hideAction)] = plot
+        view.menu.insertAction(view.menu.actions()[0], hideAction)
+
+        return plot
+
+    def _rowNumber(self, plot):
+        '''Returns the number of the first row a plot occupies.'''
+
+        # Every plot takes up two rows
+        return 2 * self._plots.index(plot)
+
+    @QtCore.Slot()
+    def _onHideActionTriggered(self, checked=False):
+        # The plot that we want to hide.
+        plot = self._hideActions[id(self.sender())]
+        self.hidePlot(plot)
+
+    def hidePlot(self, plot):
+        '''Hides 'plot'.'''
+
+        # Only hiding wouldn't give up the space occupied by the items,
+        # we have to remove them from the layout.
+        self.layout().removeItem(plot.view)
+        self.layout().removeItem(plot.xaxis)
+        self.layout().removeItem(plot.yaxis)
+
+        plot.view.hide()
+        plot.xaxis.hide()
+        plot.yaxis.hide()
+
+        row = self._rowNumber(plot)
+        self.layout().setRowStretchFactor(row,     0)
+        self.layout().setRowStretchFactor(row + 1, 0)
+
+        plot.visible = False
+        self.plotHidden.emit(plot)
+
+    def showPlot(self, plot):
+        '''Adds the items of the plot to the scene's layout and makes
+        them visible.'''
+
+        if plot.visible:
+            return
+
+        row = self._rowNumber(plot)
+        self.layout().addItem(plot.yaxis, row,     0, QtCore.Qt.AlignRight)
+        self.layout().addItem(plot.view,  row,     1)
+        self.layout().addItem(plot.xaxis, row + 1, 1)
+
+        plot.view.show()
+        plot.xaxis.show()
+        plot.yaxis.show()
 
         for i in range(row, row + 2):
-            self.layout.setRowPreferredHeight(i, 0)
-            self.layout.setRowMinimumHeight(i, 0)
-            self.layout.setRowSpacing(i, 0)
+            self.layout().setRowPreferredHeight(i, 0)
+            self.layout().setRowMinimumHeight(i, 0)
+            self.layout().setRowSpacing(i, 0)
 
-        self.layout.setRowStretchFactor(row,     100)
-        self.layout.setRowStretchFactor(row + 1,   0)
+        self.layout().setRowStretchFactor(row,     100)
+        self.layout().setRowStretchFactor(row + 1,   0)
 
-        p = MultiPlotItem.Plot(view, xaxis, yaxis)
-        self._plots.append(p)
-        return p
+        plot.visible = True
+        self.plotShown.emit()
 
 class MultiPlotWidget(pyqtgraph.GraphicsView):
     '''Widget that aligns multiple plots on top of each other.
@@ -98,6 +172,18 @@ class MultiPlotWidget(pyqtgraph.GraphicsView):
         self.setCentralItem(self.multiPlotItem)
 
         for m in [
-            'addPlot'
+            'addPlot',
+            'showPlot'
         ]:
             setattr(self, m, getattr(self.multiPlotItem, m))
+
+        self.multiPlotItem.plotShown.connect(self._on_plotShown)
+
+        # Expose the signal of the plot item.
+        self.plotHidden = self.multiPlotItem.plotHidden
+
+    def _on_plotShown(self):
+        # This call is needed if only one plot exists and it was hidden,
+        # without it the layout would start acting weird and not make the
+        # MultiPlotItem fill the view widget after showing the plot again.
+        self.resizeEvent(None)