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']
[docs]
class GraphicsLayout(GraphicsWidget):
"""
Used for laying out GraphicsWidgets in a grid.
This is usually created automatically as part of a :class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>`.
"""
[docs]
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
[docs]
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)
[docs]
def nextRow(self):
"""Advance to next row for automatic item placement"""
self.currentRow += 1
self.currentCol = -1
self.nextColumn()
[docs]
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
[docs]
def nextCol(self, *args, **kargs):
"""Alias of nextColumn"""
return self.nextColumn(*args, **kargs)
[docs]
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
[docs]
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
[docs]
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
[docs]
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
[docs]
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()
[docs]
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()
[docs]
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}")
[docs]
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()
[docs]
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)