Set GraphicsLayout proportion between plots/viewbox

4,662 views
Skip to first unread message

Pedro Paiva

unread,
Jul 4, 2013, 2:05:02 PM7/4/13
to pyqt...@googlegroups.com
Hello,

I'm trying to have 3 plots with different sizes on the screen. A big central one and 2 secondary with image projections and I want the first to occupy most of the screen and the others to be about 1/3 the size. I've tried using rowspan and colspan ona GraphicsLayout but it won't keep the dimensions... =/
Any suggestions?


mainImg = pg.ImageItem(rot90(im.light[1][0], 3)) # im.light[1][0] is a np.array float64
gLayout
= pg.GraphicsLayout()
view
.setCentralItem(gLayout)

mainHist = gLayout.addItem(pg.HistogramLUTItem(mainImg), rowspan=3)
mainBox
= gLayout.addViewBox(colspan=3, rowspan=3, lockAspect = True)
yPlot    
= gLayout.addPlot(rowspan=3)
gLayout
.nextRow()
xPlot    
= gLayout.addPlot(col=1, colspan=3)

xPlot.plot([1,3,2,4,3,5]) # Just random numbers for now
yPlot
.plot([1,3,2,4,3,5])  # Just random numbers for now
mainBox
.addItem(mainImg)
mainBox
.autoRange()
mainBox
.setMouseMode(pg.ViewBox.RectMode)



Also, is there a way to lock the mouse mode in RectMode? (I don't want to disable the mouse context menu but I don't want to allow panning...)


Thanks,
Pedro Paiva

Luke Campagnola

unread,
Jul 4, 2013, 2:49:59 PM7/4/13
to pyqt...@googlegroups.com
On Thu, Jul 4, 2013 at 2:05 PM, Pedro Paiva <p3k...@gmail.com> wrote:
Hello,

I'm trying to have 3 plots with different sizes on the screen. A big central one and 2 secondary with image projections and I want the first to occupy most of the screen and the others to be about 1/3 the size. I've tried using rowspan and colspan ona GraphicsLayout but it won't keep the dimensions... =/
Any suggestions?


mainImg = pg.ImageItem(rot90(im.light[1][0], 3)) # im.light[1][0] is a np.array float64
gLayout
= pg.GraphicsLayout()
view
.setCentralItem(gLayout)

mainHist = gLayout.addItem(pg.HistogramLUTItem(mainImg), rowspan=3)
mainBox
= gLayout.addViewBox(colspan=3, rowspan=3, lockAspect = True)
yPlot    
= gLayout.addPlot(rowspan=3)
gLayout
.nextRow()
xPlot    
= gLayout.addPlot(col=1, colspan=3)

xPlot.plot([1,3,2,4,3,5]) # Just random numbers for now
yPlot
.plot([1,3,2,4,3,5])  # Just random numbers for now
mainBox
.addItem(mainImg)
mainBox
.autoRange()
mainBox
.setMouseMode(pg.ViewBox.RectMode)




The rowspan/colspan values do not affect the relative size of items in the layout. See the Qt documentation here: http://qt-project.org/doc/qt-4.8/qgraphicsgridlayout.html
You can access the QGraphicsGridLayout as "gLayout.layout". I would recommend using setColumnMaximumWidth or something similar (there are many different choices available). 

Here's an example of what I think you're trying to build:

import numpy as np
data = np.random.normal(size=(100,100))

import pyqtgraph as pg
pg.mkQApp()
w = pg.GraphicsLayoutWidget()
w.show()
v = w.addViewBox(row=0, col=0)
p1 = w.addPlot(row=0, col=1)
p2 = w.addPlot(row=1, col=0)

# restrict size of plot areas
w.ci.layout.setColumnMaximumWidth(1, 100)
w.ci.layout.setRowMaximumHeight(1, 100)

# force central viewbox and side plots to have matching coordinate systems
p1.setYLink(v)  
p2.setXLink(v)

# Show image data and plot image mean axross x/y axes
img = pg.ImageItem(data)
v.addItem(img)
p1.plot(x=data.mean(axis=0), y=np.arange(0, data.shape[1]))
p2.plot(x=np.arange(0, data.shape[1]), y=data.mean(axis=1))



Also, is there a way to lock the mouse mode in RectMode? (I don't want to disable the mouse context menu but I don't want to allow panning...)

There is no public API for this, but you can always dig into the python structures to get what you want:

v.menu.mouseModes[0].setEnabled(False)
v.menu.mouseModes[1].setEnabled(False)

Just understand that if we change the internal structure in the future, this code will stop working.



Luke

Pedro Paiva

unread,
Jul 8, 2013, 5:59:10 AM7/8/13
to pyqt...@googlegroups.com
Thanks for the help! It sure send me on the right way! xD

My solution was to grab the resizeEven, recalculate the proportions and set the geometry/preferred size of each graph...


Thanks,
Pedro Paiva

Luke Campagnola

unread,
Jul 8, 2013, 10:42:05 AM7/8/13
to pyqt...@googlegroups.com
On Mon, Jul 8, 2013 at 5:59 AM, Pedro Paiva <p3k...@gmail.com> wrote:
Thanks for the help! It sure send me on the right way! xD

My solution was to grab the resizeEven, recalculate the proportions and set the geometry/preferred size of each graph...

It sounds like your solution has the same effect as using layout.setColumnStretchFactor and setRowStretchFactor. Is that correct?
(Not that there is anything wrong with your solution--I still find the behavior of layouts to be confusing. Sometimes it's easiest to just write the code yourself.)


Luke


Pedro Paiva

unread,
Jul 8, 2013, 11:12:51 AM7/8/13
to pyqt...@googlegroups.com
Hmm, I don't think so... the Stretch Factor set how they grow/shrink, right?
My graphs still grow with one to one factor when possible... I do also think the layouts behaviour very confusing... but it seems to be working nicely now... xD

Pedro Paiva

unread,
Jul 10, 2013, 9:29:25 AM7/10/13
to pyqt...@googlegroups.com
Hello again! xD

I'm having problems with the YLink... It's not really aligned with the viewbox (view image)
Is there a way to force realign? Or something I could do?

Luke Campagnola

unread,
Jul 10, 2013, 11:24:24 AM7/10/13
to pyqt...@googlegroups.com
On Wed, Jul 10, 2013 at 9:29 AM, Pedro Paiva <p3k...@gmail.com> wrote:
Hello again! xD

I'm having problems with the YLink... It's not really aligned with the viewbox (view image)
Is there a way to force realign? Or something I could do?

I need more information.. a working code example is usually the best way for me to help.


Luke 

Pedro Paiva

unread,
Jul 10, 2013, 12:11:50 PM7/10/13
to pyqt...@googlegroups.com
I cleaned my code the most I dared... xP
(try aligning the cross hair with the other graphics and look at the x, y, pos displayed as well)

And here is the img I forgot to insert on my last post... (with my full app so far)




# Import PySide Qt elements
import PySide
from PySide.QtGui import *
from PySide.QtCore import *
import pyqtgraph as pg

# Import pylab and sys
from pylab import *
import sys

# PyQtGraph
import pyqtgraph as pg


class mainWindow(QMainWindow):
    def __init__(self):
        super(mainWindow, self).__init__()

        self.central = central()
        self.setCentralWidget(self.central)
        
    def resizeEvent(self, event):
        self.central.layout.resized()



class central(QWidget):
    def __init__(self):
        super(central, self).__init__()
        self.setFocus()
        self.layout = centralLayout()
        self.setLayout(self.layout)


class centralLayout(QGridLayout):
    def __init__(self):
        super(centralLayout, self).__init__()
        
        view = pg.GraphicsView()
        
        self.im = images()
                               
        self.gLayout = pg.GraphicsLayout()
        view.setCentralItem(self.gLayout)
        
        self.label = pg.LabelItem(justify='center')
        self.label.hide()
        self.gLayout.addItem(self.label, row = 1, col = 1)

        self.mainBox = myViewBox(self, lockAspect = True)
        self.mainBox.images = {}
        
        self.gLayout.addItem(self.mainBox, row = 0, col = 0)
        
        self.yPlot = self.gLayout.addPlot(row=0, col=1)
        self.yPlot.invertY()
        self.yPlot.setYLink(self.mainBox)
        self.yPlot.setAutoVisible(x=True)
        self.yPlot.showGrid(x=True, y=True)
        
        self.xPlot = self.gLayout.addPlot(row = 1, col = 0)
        self.xPlot.setXLink(self.mainBox)
        self.xPlot.setAutoVisible(y=True)
        self.xPlot.showGrid(x=True, y=True)
        
        self.mainHist = pg.HistogramLUTItem()
        self.gLayout.addItem(self.mainHist, row = 0, col = 2, rowspan = 2)

        
        self.mainBox.autoRange()
        self.mainBox.scene().sigMouseMoved.connect(self.mainBox.mouseMoved)

        view.show()

        self.show(0, 100)
        
        self.addWidget(view)

    def show(self, priority, alpha, show=True):
        image = 1000 * rand(1000,1000)
        imgMedian = median(image)
        img = pg.ImageItem((image), levels = (imgMedian, 3*imgMedian), opacity = alpha/100.)
        img.setZValue(priority)
        img.setData(0, key)
        
        self.mainBox.addItem(img)
        self.mainBox.autoRange()
        
        self.mainHist.setImageItem(img)
        self.mainHist.setHistogramRange(imgMedian, 3*imgMedian)
        self.mainHist.setLevels(imgMedian, 3*imgMedian)
        
        self.xPlot.plot(image.sum(1))
        self.yPlot.plot(x = image.sum(0), y = indices(image.sum(0).shape)[0] )


    def resized(self):
        range = self.mainBox.viewRange()
        X, Y = range[0][1], range[1][1]
        relax = 0.05 * X    # Set size for between graphs
        proj  = 0.3  * Y    # Set size of projections
        width = X + relax + 2*proj
        
        totalSize = self.gLayout.layout.geometry()  # Get widget geometry
        
        # Scales dimensions based on viebox image and widget size
        scale = totalSize.width() / width
        
        newX = X * scale
        newY = Y * scale
        newRelax = relax * scale / 4
        newProj = proj * scale
        heighRelax = (totalSize.height() - (newY + newProj)) / 3
        
        # Set new geometries and define a preferred size
        self.gLayout.layout.itemAt(0, 0).setGeometry(newRelax, heighRelax, newX, newY)
        self.gLayout.layout.itemAt(0, 0).setPreferredSize(self.gLayout.layout.itemAt(0, 0).geometry().size())
        self.gLayout.layout.itemAt(0, 0).updateGeometry()
        self.gLayout.layout.itemAt(1, 0).setGeometry(newRelax, (newY + 2*heighRelax), newX, newProj)
        self.gLayout.layout.itemAt(1, 0).setPreferredSize(self.gLayout.layout.itemAt(1, 0).geometry().size())
        self.gLayout.layout.itemAt(1, 0).updateGeometry()
        self.gLayout.layout.itemAt(0, 1).setGeometry((newX + 2*newRelax), heighRelax, newProj, newY)
        self.gLayout.layout.itemAt(0, 1).setPreferredSize(self.gLayout.layout.itemAt(0, 1).geometry().size())
        self.gLayout.layout.itemAt(0, 1).updateGeometry()
#         self.gLayout.layout.itemAt(0, 2).setGeometry(totalSize.width()-120-newRelax, heighRelax, newProj, (totalSize.height()-2*heighRelax) )
#         self.gLayout.layout.itemAt(0, 2).setPreferredSize(self.gLayout.layout.itemAt(0, 2).geometry().size())
#         self.gLayout.layout.itemAt(0, 2).updateGeometry()
        
        
class myViewBox(pg.ViewBox):
    def __init__(self, par, *args, **kwds):
        self.par = par
        
        pg.ViewBox.__init__(self, *args, **kwds)
        self.setMouseMode(self.RectMode)
        self.invertY()

        self.vLine = pg.InfiniteLine(angle=90, movable=False)
        self.vLine.setZValue(100)
        self.vLine.hide()
        self.hLine = pg.InfiniteLine(angle=0, movable=False)
        self.hLine.setZValue(100)
        self.vLine.hide()
        self.addItem(self.vLine, ignoreBounds=True)
        self.addItem(self.hLine, ignoreBounds=True)
        
        self.dragVLine = pg.InfiniteLine(angle=90, movable=False)
        self.dragVLine.setZValue(100)
        self.dragVLine.hide()
        self.dragHLine = pg.InfiniteLine(angle=0, movable=False)
        self.dragHLine.setZValue(100)
        self.dragHLine.hide()
        self.addItem(self.dragVLine, ignoreBounds=True)
        self.addItem(self.dragHLine, ignoreBounds=True)
     

    def mouseDragEvent(self, ev):
        if ev.button() == Qt.RightButton:
            ev.ignore()
        else:
            self.dragVLine.setPos(self.mapToView(ev.buttonDownPos()).x())
            self.dragVLine.show()
            self.dragHLine.setPos(self.mapToView(ev.buttonDownPos()).y())
            self.dragHLine.show()

            pg.ViewBox.mouseDragEvent(self, ev)

        if ev.isFinish():
            self.vLine.hide()
            self.hLine.hide()
            self.dragVLine.hide()
            self.dragHLine.hide()


    def mouseMoved(self, ev):
#         Make show only inside image, make show pixel value as well!
        pos = ev
        if self.sceneBoundingRect().contains(pos):
            mousePoint = self.mapSceneToView(pos)
            index = [int(mousePoint.x()), int(mousePoint.y())]
            if index[0] > 0 and index[1] > 0 and index[0] < self.viewRange()[0] and index[1] < self.viewRange()[1]:
                self.par.label.show()
                self.par.label.setText("<span style='font-size: 14pt'><br><br><br> x = %4.0f <br> y = %4.0f <br>" % (mousePoint.x(), mousePoint.y()))
            else:
                self.par.label.hide()
            self.vLine.setPos(mousePoint.x())
            self.vLine.show()
            self.hLine.setPos(mousePoint.y())
            self.hLine.show()
        else:
            self.par.label.hide()
            self.vLine.hide()
            self.hLine.hide()

def main():
    app = QApplication.instance()   # checks if QApplication already exists
    if not app:                     # create QApplication if it doesnt exist
        app = QApplication(sys.argv)
    main = mainWindow()
    main.show()
#    sys.exit(app.exec_())
    app.exec_()


if __name__ == '__main__':
    main()

Pedro Paiva

unread,
Jul 10, 2013, 12:22:06 PM7/10/13
to pyqt...@googlegroups.com
Sorry, there's two extra line in the code above, I'll just post a really working version here...

        img.setData(0, 'rand img')

Luke Campagnola

unread,
Jul 10, 2013, 1:26:59 PM7/10/13
to pyqt...@googlegroups.com
Ah-HA! The offset you are seeing occurs because you have the y-axis inverted on the right plot, but not in the image view. I'm actually not even sure this is something I can fix on my end--it is not really meaningful to link two axes that have opposite orientations. Better to leave the Y-axes unchanged and plot your data in the correct direction.  I could at least add a helpful error message in this case..


Luke

Pedro Paiva

unread,
Jul 10, 2013, 1:50:25 PM7/10/13
to pyqt...@googlegroups.com
hmm, no...

On the myViewBox class I have a 'self.invertY()'
So both are inverted, right?

Luke Campagnola

unread,
Jul 10, 2013, 2:37:20 PM7/10/13
to pyqt...@googlegroups.com
Correct! My mistake, this is a bug.  
You can copy in the specific changes there; I would not recommend copying the whole file unless you are already using the code from that branch. 
These will appear in the next release. Thanks for being patient with me :)


Luke

Pedro Paiva

unread,
Jul 10, 2013, 6:27:23 PM7/10/13
to pyqt...@googlegroups.com
Yey, thank you very much for the fix! xD

And thanks for the awesome package the pyqtgraph is and all the support offered in this group! xD


Pedro
Reply all
Reply to author
Forward
0 new messages