]> sigrok.org Git - sigrok-meter.git/blob - multiplotwidget.py
doc: update IRC reference to Libera.Chat
[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, see <http://www.gnu.org/licenses/>.
18 ##
19
20 import qtcompat
21
22 QtCore = qtcompat.QtCore
23 QtGui = qtcompat.QtGui
24 pyqtgraph = qtcompat.pyqtgraph
25
26 # Black foreground on white background.
27 pyqtgraph.setConfigOption('background', 'w')
28 pyqtgraph.setConfigOption('foreground', 'k')
29
30 class Plot(object):
31     '''Helper class to keep all graphics items of a plot together.'''
32
33     def __init__(self, view, xaxis, yaxis):
34         self.view = view
35         self.xaxis = xaxis
36         self.yaxis = yaxis
37         self.visible = False
38
39 class MultiPlotItem(pyqtgraph.GraphicsWidget):
40
41     # Emitted when a plot is shown.
42     plotShown = QtCore.Signal()
43
44     # Emitted when a plot is hidden by the user via the context menu.
45     plotHidden = QtCore.Signal(Plot)
46
47     def __init__(self, parent=None):
48         pyqtgraph.GraphicsWidget.__init__(self, parent)
49
50         self.setLayout(QtGui.QGraphicsGridLayout())
51         self.layout().setContentsMargins(10, 10, 10, 1)
52         self.layout().setHorizontalSpacing(0)
53         self.layout().setVerticalSpacing(0)
54
55         for i in range(2):
56             self.layout().setColumnPreferredWidth(i, 0)
57             self.layout().setColumnMinimumWidth(i, 0)
58             self.layout().setColumnSpacing(i, 0)
59
60         self.layout().setColumnStretchFactor(0, 0)
61         self.layout().setColumnStretchFactor(1, 100)
62
63         # List of 'Plot' objects that are shown.
64         self._plots = []
65
66         self._hideActions = {}
67
68     def addPlot(self):
69         '''Adds and returns a new plot.'''
70
71         row = self.layout().rowCount()
72
73         view = pyqtgraph.ViewBox(parent=self)
74
75         # If this is not the first plot, link to the axis of the previous one.
76         if self._plots:
77             view.setXLink(self._plots[-1].view)
78
79         yaxis = pyqtgraph.AxisItem(parent=self, orientation='left')
80         yaxis.linkToView(view)
81         yaxis.setGrid(255)
82
83         xaxis = pyqtgraph.AxisItem(parent=self, orientation='bottom')
84         xaxis.linkToView(view)
85         xaxis.setGrid(255)
86
87         plot = Plot(view, xaxis, yaxis)
88         self._plots.append(plot)
89
90         self.showPlot(plot)
91
92         # Create a separate action object for each plots context menu, so that
93         # we can later find out which plot should be hidden by looking at
94         # 'self._hideActions'.
95         hideAction = QtGui.QAction('Hide', self)
96         hideAction.triggered.connect(self._onHideActionTriggered)
97         self._hideActions[id(hideAction)] = plot
98         view.menu.insertAction(view.menu.actions()[0], hideAction)
99
100         return plot
101
102     def _rowNumber(self, plot):
103         '''Returns the number of the first row a plot occupies.'''
104
105         # Every plot takes up two rows.
106         return 2 * self._plots.index(plot)
107
108     @QtCore.Slot()
109     def _onHideActionTriggered(self, checked=False):
110         # The plot that we want to hide.
111         plot = self._hideActions[id(self.sender())]
112         self.hidePlot(plot)
113
114     def hidePlot(self, plot):
115         '''Hides 'plot'.'''
116
117         # Only hiding wouldn't give up the space occupied by the items,
118         # we have to remove them from the layout.
119         self.layout().removeItem(plot.view)
120         self.layout().removeItem(plot.xaxis)
121         self.layout().removeItem(plot.yaxis)
122
123         plot.view.hide()
124         plot.xaxis.hide()
125         plot.yaxis.hide()
126
127         row = self._rowNumber(plot)
128         self.layout().setRowStretchFactor(row,     0)
129         self.layout().setRowStretchFactor(row + 1, 0)
130
131         plot.visible = False
132         self.plotHidden.emit(plot)
133
134     def showPlot(self, plot):
135         '''Adds the items of the plot to the scene's layout and makes
136         them visible.'''
137
138         if plot.visible:
139             return
140
141         row = self._rowNumber(plot)
142         self.layout().addItem(plot.yaxis, row,     0, QtCore.Qt.AlignRight)
143         self.layout().addItem(plot.view,  row,     1)
144         self.layout().addItem(plot.xaxis, row + 1, 1)
145
146         plot.view.show()
147         plot.xaxis.show()
148         plot.yaxis.show()
149
150         for i in range(row, row + 2):
151             self.layout().setRowPreferredHeight(i, 0)
152             self.layout().setRowMinimumHeight(i, 0)
153             self.layout().setRowSpacing(i, 0)
154
155         self.layout().setRowStretchFactor(row,     100)
156         self.layout().setRowStretchFactor(row + 1,   0)
157
158         plot.visible = True
159         self.plotShown.emit()
160
161 class MultiPlotWidget(pyqtgraph.GraphicsView):
162     '''Widget that aligns multiple plots on top of each other.
163
164     (The built in classes fail at doing this correctly when the axis grow,
165     just try zooming in the "GraphicsLayout" or the "Linked View" examples.)'''
166
167     def __init__(self, parent=None):
168         pyqtgraph.GraphicsView.__init__(self, parent)
169
170         self.multiPlotItem = MultiPlotItem()
171         self.setCentralItem(self.multiPlotItem)
172
173         for m in [
174             'addPlot',
175             'hidePlot',
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)