]> sigrok.org Git - sigrok-meter.git/blame - multiplotwidget.py
license: remove FSF postal address from boiler plate license text
[sigrok-meter.git] / multiplotwidget.py
CommitLineData
f76b9df8
JS
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
0b63748b 17## along with this program; if not, see <http://www.gnu.org/licenses/>.
f76b9df8
JS
18##
19
20import qtcompat
21
22QtCore = qtcompat.QtCore
23QtGui = qtcompat.QtGui
24pyqtgraph = qtcompat.pyqtgraph
25
911ab26e 26# Black foreground on white background.
f76b9df8
JS
27pyqtgraph.setConfigOption('background', 'w')
28pyqtgraph.setConfigOption('foreground', 'k')
29
d0aa45b4
JS
30class 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
f76b9df8
JS
39class MultiPlotItem(pyqtgraph.GraphicsWidget):
40
d0aa45b4
JS
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)
f76b9df8
JS
46
47 def __init__(self, parent=None):
48 pyqtgraph.GraphicsWidget.__init__(self, parent)
49
d0aa45b4
JS
50 self.setLayout(QtGui.QGraphicsGridLayout())
51 self.layout().setContentsMargins(10, 10, 10, 1)
52 self.layout().setHorizontalSpacing(0)
53 self.layout().setVerticalSpacing(0)
f76b9df8
JS
54
55 for i in range(2):
d0aa45b4
JS
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 = []
f76b9df8 65
d0aa45b4 66 self._hideActions = {}
f76b9df8
JS
67
68 def addPlot(self):
d0aa45b4
JS
69 '''Adds and returns a new plot.'''
70
71 row = self.layout().rowCount()
f76b9df8
JS
72
73 view = pyqtgraph.ViewBox(parent=self)
d0aa45b4
JS
74
75 # If this is not the first plot, link to the axis of the previous one.
f76b9df8
JS
76 if self._plots:
77 view.setXLink(self._plots[-1].view)
f76b9df8
JS
78
79 yaxis = pyqtgraph.AxisItem(parent=self, orientation='left')
80 yaxis.linkToView(view)
81 yaxis.setGrid(255)
f76b9df8
JS
82
83 xaxis = pyqtgraph.AxisItem(parent=self, orientation='bottom')
84 xaxis.linkToView(view)
85 xaxis.setGrid(255)
d0aa45b4
JS
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
911ab26e 105 # Every plot takes up two rows.
d0aa45b4
JS
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()
f76b9df8
JS
149
150 for i in range(row, row + 2):
d0aa45b4
JS
151 self.layout().setRowPreferredHeight(i, 0)
152 self.layout().setRowMinimumHeight(i, 0)
153 self.layout().setRowSpacing(i, 0)
f76b9df8 154
d0aa45b4
JS
155 self.layout().setRowStretchFactor(row, 100)
156 self.layout().setRowStretchFactor(row + 1, 0)
f76b9df8 157
d0aa45b4
JS
158 plot.visible = True
159 self.plotShown.emit()
f76b9df8
JS
160
161class 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 [
d0aa45b4 174 'addPlot',
68348e5a 175 'hidePlot',
d0aa45b4 176 'showPlot'
f76b9df8
JS
177 ]:
178 setattr(self, m, getattr(self.multiPlotItem, m))
d0aa45b4
JS
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)