1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
|
from .. import functions as fn
from ..Qt import QtWidgets
from .GraphicsWidget import GraphicsWidget
from .LabelItem import LabelItem
from .PlotItem import PlotItem
## Must be imported at the end to avoid cyclic-dependency hell:
from .ViewBox import ViewBox
__all__ = ['GraphicsLayout']
class GraphicsLayout(GraphicsWidget):
"""
Used for laying out GraphicsWidgets in a grid.
This is usually created automatically as part of a :class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>`.
"""
def __init__(self, parent=None, border=None):
GraphicsWidget.__init__(self, parent)
if border is True:
border = (100,100,100)
elif border is False:
border = None
self.border = border
self.layout = QtWidgets.QGraphicsGridLayout()
self.setLayout(self.layout)
self.items = {} ## item: [(row, col), (row, col), ...] lists all cells occupied by the item
self.rows = {} ## row: {col1: item1, col2: item2, ...} maps cell location to item
self.itemBorders = {} ## {item1: QtWidgets.QGraphicsRectItem, ...} border rects
self.currentRow = 0
self.currentCol = 0
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding))
#def resizeEvent(self, ev):
#ret = GraphicsWidget.resizeEvent(self, ev)
#print self.pos(), self.mapToDevice(self.rect().topLeft())
#return ret
def setBorder(self, *args, **kwds):
"""
Set the pen used to draw border between cells.
See :func:`mkPen <pyqtgraph.mkPen>` for arguments.
"""
self.border = fn.mkPen(*args, **kwds)
for borderRect in self.itemBorders.values():
borderRect.setPen(self.border)
def nextRow(self):
"""Advance to next row for automatic item placement"""
self.currentRow += 1
self.currentCol = -1
self.nextColumn()
def nextColumn(self):
"""Advance to next available column
(generally only for internal use--called by addItem)"""
self.currentCol += 1
while self.getItem(self.currentRow, self.currentCol) is not None:
self.currentCol += 1
def nextCol(self, *args, **kargs):
"""Alias of nextColumn"""
return self.nextColumn(*args, **kargs)
def addPlot(self, row=None, col=None, rowspan=1, colspan=1, **kargs):
"""
Create a PlotItem and place it in the next available cell (or in the cell specified)
All extra keyword arguments are passed to :func:`PlotItem.__init__ <pyqtgraph.PlotItem.__init__>`
Returns the created item.
"""
plot = PlotItem(**kargs)
self.addItem(plot, row, col, rowspan, colspan)
return plot
def addViewBox(self, row=None, col=None, rowspan=1, colspan=1, **kargs):
"""
Create a ViewBox and place it in the next available cell (or in the cell specified)
All extra keyword arguments are passed to :func:`ViewBox.__init__ <pyqtgraph.ViewBox.__init__>`
Returns the created item.
"""
vb = ViewBox(**kargs)
self.addItem(vb, row, col, rowspan, colspan)
return vb
def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs):
"""
Create a LabelItem with *text* and place it in the next available cell (or in the cell specified)
All extra keyword arguments are passed to :func:`LabelItem.__init__ <pyqtgraph.LabelItem.__init__>`
Returns the created item.
To create a vertical label, use *angle* = -90.
"""
text = LabelItem(text, **kargs)
self.addItem(text, row, col, rowspan, colspan)
return text
def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs):
"""
Create an empty GraphicsLayout and place it in the next available cell (or in the cell specified)
All extra keyword arguments are passed to :func:`GraphicsLayout.__init__ <pyqtgraph.GraphicsLayout.__init__>`
Returns the created item.
"""
layout = GraphicsLayout(**kargs)
self.addItem(layout, row, col, rowspan, colspan)
return layout
def addItem(self, item, row=None, col=None, rowspan=1, colspan=1):
"""
Add an item to the layout and place it in the next available cell (or in the cell specified).
The item must be an instance of a QGraphicsWidget subclass.
"""
if row is None:
row = self.currentRow
if col is None:
col = self.currentCol
self.items[item] = []
for i in range(rowspan):
for j in range(colspan):
row2 = row + i
col2 = col + j
if row2 not in self.rows:
self.rows[row2] = {}
self.rows[row2][col2] = item
self.items[item].append((row2, col2))
borderRect = QtWidgets.QGraphicsRectItem()
borderRect.setParentItem(self)
borderRect.setZValue(1e3)
borderRect.setPen(fn.mkPen(self.border))
self.itemBorders[item] = borderRect
item.geometryChanged.connect(self._updateItemBorder)
self.layout.addItem(item, row, col, rowspan, colspan)
self.layout.activate() # Update layout, recalculating bounds.
# Allows some PyQtGraph features to also work without Qt event loop.
self.nextColumn()
def getItem(self, row, col):
"""Return the item in (*row*, *col*). If the cell is empty, return None."""
return self.rows.get(row, {}).get(col, None)
def boundingRect(self):
return self.rect()
def itemIndex(self, item):
"""Return the numerical index of GraphicsItem object passed in
Parameters
----------
item : QGraphicsLayoutItem
Item to query the index position of
Returns
-------
int
Index of the item within the graphics layout
Raises
------
ValueError
Raised if item could not be found inside the GraphicsLayout instance.
"""
for i in range(self.layout.count()):
if self.layout.itemAt(i).graphicsItem() is item:
return i
raise ValueError(f"Could not determine index of item {item}")
def removeItem(self, item):
"""Remove *item* from the layout."""
ind = self.itemIndex(item)
self.layout.removeAt(ind)
self.scene().removeItem(item)
for r, c in self.items[item]:
del self.rows[r][c]
del self.items[item]
item.geometryChanged.disconnect(self._updateItemBorder)
itemBorder = self.itemBorders.pop(item)
self.scene().removeItem(itemBorder)
self.update()
def clear(self):
"""Remove all items from the layout and set the current row and column to 0
"""
for i in list(self.items.keys()):
self.removeItem(i)
self.currentRow = 0
self.currentCol = 0
def setContentsMargins(self, *args):
# Wrap calls to layout. This should happen automatically, but there
# seems to be a Qt bug:
# http://stackoverflow.com/questions/27092164/margins-in-pyqtgraphs-graphicslayout
self.layout.setContentsMargins(*args)
def setSpacing(self, *args):
self.layout.setSpacing(*args)
def _updateItemBorder(self):
if self.border is None:
return
item = self.sender()
if item is None:
return
r = item.mapRectToParent(item.boundingRect())
self.itemBorders[item].setRect(r)
|