]> sigrok.org Git - sigrok-meter.git/blob - multiplotwidget.py
Timestamp measurements as early as possible.
[sigrok-meter.git] / multiplotwidget.py
1 ##
2 ## This file is part of the sigrok-meter project.
3 ##
4 ## Copyright (C) 2015 Jens Steinhauser <jens.steinhauser@gmail.com>
5 ##
6 ## This program is free software; you can redistribute it and/or modify
7 ## it under the terms of the GNU General Public License as published by
8 ## the Free Software Foundation; either version 2 of the License, or
9 ## (at your option) any later version.
10 ##
11 ## This program is distributed in the hope that it will be useful,
12 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 ## GNU General Public License for more details.
15 ##
16 ## You should have received a copy of the GNU General Public License
17 ## along with this program; if not, write to the Free Software
18 ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 ##
20
21 import qtcompat
22
23 QtCore = qtcompat.QtCore
24 QtGui = qtcompat.QtGui
25 pyqtgraph = qtcompat.pyqtgraph
26
27 # black foreground on white background
28 pyqtgraph.setConfigOption('background', 'w')
29 pyqtgraph.setConfigOption('foreground', 'k')
30
31 class Plot(object):
32     '''Helper class to keep all graphics items of a plot together.'''
33
34     def __init__(self, view, xaxis, yaxis):
35         self.view = view
36         self.xaxis = xaxis
37         self.yaxis = yaxis
38         self.visible = False
39
40 class MultiPlotItem(pyqtgraph.GraphicsWidget):
41
42     # Emitted when a plot is shown.
43     plotShown = QtCore.Signal()
44
45     # Emitted when a plot is hidden by the user via the context menu.
46     plotHidden = QtCore.Signal(Plot)
47
48     def __init__(self, parent=None):
49         pyqtgraph.GraphicsWidget.__init__(self, parent)
50
51         self.setLayout(QtGui.QGraphicsGridLayout())
52         self.layout().setContentsMargins(10, 10, 10, 1)
53         self.layout().setHorizontalSpacing(0)
54         self.layout().setVerticalSpacing(0)
55
56         for i in range(2):
57             self.layout().setColumnPreferredWidth(i, 0)
58             self.layout().setColumnMinimumWidth(i, 0)
59             self.layout().setColumnSpacing(i, 0)
60
61         self.layout().setColumnStretchFactor(0, 0)
62         self.layout().setColumnStretchFactor(1, 100)
63
64         # List of 'Plot' objects that are shown.
65         self._plots = []
66
67         self._hideActions = {}
68
69     def addPlot(self):
70         '''Adds and returns a new plot.'''
71
72         row = self.layout().rowCount()
73
74         view = pyqtgraph.ViewBox(parent=self)
75
76         # If this is not the first plot, link to the axis of the previous one.
77         if self._plots:
78             view.setXLink(self._plots[-1].view)
79
80         yaxis = pyqtgraph.AxisItem(parent=self, orientation='left')
81         yaxis.linkToView(view)
82         yaxis.setGrid(255)
83
84         xaxis = pyqtgraph.AxisItem(parent=self, orientation='bottom')
85         xaxis.linkToView(view)
86         xaxis.setGrid(255)
87
88         plot = Plot(view, xaxis, yaxis)
89         self._plots.append(plot)
90
91         self.showPlot(plot)
92
93         # Create a separate action object for each plots context menu, so that
94         # we can later find out which plot should be hidden by looking at
95         # 'self._hideActions'.
96         hideAction = QtGui.QAction('Hide', self)
97         hideAction.triggered.connect(self._onHideActionTriggered)
98         self._hideActions[id(hideAction)] = plot
99         view.menu.insertAction(view.menu.actions()[0], hideAction)
100
101         return plot
102
103     def _rowNumber(self, plot):
104         '''Returns the number of the first row a plot occupies.'''
105
106         # Every plot takes up two rows
107         return 2 * self._plots.index(plot)
108
109     @QtCore.Slot()
110     def _onHideActionTriggered(self, checked=False):
111         # The plot that we want to hide.
112         plot = self._hideActions[id(self.sender())]
113         self.hidePlot(plot)
114
115     def hidePlot(self, plot):
116         '''Hides 'plot'.'''
117
118         # Only hiding wouldn't give up the space occupied by the items,
119         # we have to remove them from the layout.
120         self.layout().removeItem(plot.view)
121         self.layout().removeItem(plot.xaxis)
122         self.layout().removeItem(plot.yaxis)
123
124         plot.view.hide()
125         plot.xaxis.hide()
126         plot.yaxis.hide()
127
128         row = self._rowNumber(plot)
129         self.layout().setRowStretchFactor(row,     0)
130         self.layout().setRowStretchFactor(row + 1, 0)
131
132         plot.visible = False
133         self.plotHidden.emit(plot)
134
135     def showPlot(self, plot):
136         '''Adds the items of the plot to the scene's layout and makes
137         them visible.'''
138
139         if plot.visible:
140             return
141
142         row = self._rowNumber(plot)
143         self.layout().addItem(plot.yaxis, row,     0, QtCore.Qt.AlignRight)
144         self.layout().addItem(plot.view,  row,     1)
145         self.layout().addItem(plot.xaxis, row + 1, 1)
146
147         plot.view.show()
148         plot.xaxis.show()
149         plot.yaxis.show()
150
151         for i in range(row, row + 2):
152             self.layout().setRowPreferredHeight(i, 0)
153             self.layout().setRowMinimumHeight(i, 0)
154             self.layout().setRowSpacing(i, 0)
155
156         self.layout().setRowStretchFactor(row,     100)
157         self.layout().setRowStretchFactor(row + 1,   0)
158
159         plot.visible = True
160         self.plotShown.emit()
161
162 class MultiPlotWidget(pyqtgraph.GraphicsView):
163     '''Widget that aligns multiple plots on top of each other.
164
165     (The built in classes fail at doing this correctly when the axis grow,
166     just try zooming in the "GraphicsLayout" or the "Linked View" examples.)'''
167
168     def __init__(self, parent=None):
169         pyqtgraph.GraphicsView.__init__(self, parent)
170
171         self.multiPlotItem = MultiPlotItem()
172         self.setCentralItem(self.multiPlotItem)
173
174         for m in [
175             'addPlot',
176             'showPlot'
177         ]:
178             setattr(self, m, getattr(self.multiPlotItem, m))
179
180         self.multiPlotItem.plotShown.connect(self._on_plotShown)
181
182         # Expose the signal of the plot item.
183         self.plotHidden = self.multiPlotItem.plotHidden
184
185     def _on_plotShown(self):
186         # This call is needed if only one plot exists and it was hidden,
187         # without it the layout would start acting weird and not make the
188         # MultiPlotItem fill the view widget after showing the plot again.
189         self.resizeEvent(None)