GraphicsLayout.py 6.34 KB
Newer Older
1 2
from ..Qt import QtGui, QtCore
from .. import functions as fn
3
from .GraphicsWidget import GraphicsWidget
Luke Campagnola's avatar
Luke Campagnola committed
4 5 6 7
## Must be imported at the end to avoid cyclic-dependency hell:
from .ViewBox import ViewBox
from .PlotItem import PlotItem
from .LabelItem import LabelItem
8 9 10 11 12

__all__ = ['GraphicsLayout']
class GraphicsLayout(GraphicsWidget):
    """
    Used for laying out GraphicsWidgets in a grid.
Luke Campagnola's avatar
Luke Campagnola committed
13
    This is usually created automatically as part of a :class:`GraphicsWindow <pyqtgraph.GraphicsWindow>` or :class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>`.
14 15 16 17 18 19 20 21 22 23
    """


    def __init__(self, parent=None, border=None):
        GraphicsWidget.__init__(self, parent)
        if border is True:
            border = (100,100,100)
        self.border = border
        self.layout = QtGui.QGraphicsGridLayout()
        self.setLayout(self.layout)
24 25
        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
26 27
        self.currentRow = 0
        self.currentCol = 0
28 29 30 31 32 33
        self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding))
    
    #def resizeEvent(self, ev):
        #ret = GraphicsWidget.resizeEvent(self, ev)
        #print self.pos(), self.mapToDevice(self.rect().topLeft())
        #return ret
34 35 36 37 38 39 40 41 42

    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)
        self.update()
43 44 45 46
    
    def nextRow(self):
        """Advance to next row for automatic item placement"""
        self.currentRow += 1
47 48
        self.currentCol = -1
        self.nextColumn()
49
        
50 51
    def nextColumn(self):
        """Advance to next available column
52
        (generally only for internal use--called by addItem)"""
53 54 55
        self.currentCol += 1
        while self.getItem(self.currentRow, self.currentCol) is not None:
            self.currentCol += 1
56
        
57
    def nextCol(self, *args, **kargs):
Luke Campagnola's avatar
Luke Campagnola committed
58
        """Alias of nextColumn"""
59 60
        return self.nextColumn(*args, **kargs)
        
61
    def addPlot(self, row=None, col=None, rowspan=1, colspan=1, **kargs):
Luke Campagnola's avatar
Luke Campagnola committed
62 63 64 65 66
        """
        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.
        """
67 68 69 70 71
        plot = PlotItem(**kargs)
        self.addItem(plot, row, col, rowspan, colspan)
        return plot
        
    def addViewBox(self, row=None, col=None, rowspan=1, colspan=1, **kargs):
Luke Campagnola's avatar
Luke Campagnola committed
72 73 74 75 76
        """
        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.
        """
77 78 79 80
        vb = ViewBox(**kargs)
        self.addItem(vb, row, col, rowspan, colspan)
        return vb
        
81
    def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs):
Luke Campagnola's avatar
Luke Campagnola committed
82 83 84 85
        """
        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.
86
        
Luke Campagnola's avatar
Luke Campagnola committed
87
        To create a vertical label, use *angle* = -90.
Luke Campagnola's avatar
Luke Campagnola committed
88
        """
89 90 91 92
        text = LabelItem(text, **kargs)
        self.addItem(text, row, col, rowspan, colspan)
        return text
        
93
    def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs):
Luke Campagnola's avatar
Luke Campagnola committed
94 95 96 97 98
        """
        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.
        """
99 100 101 102
        layout = GraphicsLayout(**kargs)
        self.addItem(layout, row, col, rowspan, colspan)
        return layout
        
103
    def addItem(self, item, row=None, col=None, rowspan=1, colspan=1):
Luke Campagnola's avatar
Luke Campagnola committed
104 105 106 107
        """
        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.
        """
108 109 110
        if row is None:
            row = self.currentRow
        if col is None:
111
            col = self.currentCol
112
            
113 114 115 116 117 118 119 120 121
        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))
122 123
        
        self.layout.addItem(item, row, col, rowspan, colspan)
124
        self.nextColumn()
125 126

    def getItem(self, row, col):
127 128
        """Return the item in (*row*, *col*). If the cell is empty, return None."""
        return self.rows.get(row, {}).get(col, None)
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147

    def boundingRect(self):
        return self.rect()
        
    def paint(self, p, *args):
        if self.border is None:
            return
        p.setPen(fn.mkPen(self.border))
        for i in self.items:
            r = i.mapRectToParent(i.boundingRect())
            p.drawRect(r)
    
    def itemIndex(self, item):
        for i in range(self.layout.count()):
            if self.layout.itemAt(i).graphicsItem() is item:
                return i
        raise Exception("Could not determine index of item " + str(item))
    
    def removeItem(self, item):
Luke Campagnola's avatar
Luke Campagnola committed
148
        """Remove *item* from the layout."""
149 150 151
        ind = self.itemIndex(item)
        self.layout.removeAt(ind)
        self.scene().removeItem(item)
152 153 154
        
        for r,c in self.items[item]:
            del self.rows[r][c]
155 156 157 158 159
        del self.items[item]
        self.update()
    
    def clear(self):
        items = []
160
        for i in list(self.items.keys()):
161 162
            self.removeItem(i)

163 164 165 166 167
    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)
168

169 170 171
    def setSpacing(self, *args):
        self.layout.setSpacing(*args)