Repository: pyqtgraph/pyqtgraph

Issue:
an issue on ViewBox.mapToView
Hi, I do love pyqtgraph, it really save my life, but there is a little problem in it. 

At first start of pg.GraphicsWindow, it seems that ViewBox return (0, 0) size by ViewBox.rect() method, which lead to ViewBox.updateMatrix get 0 size of bound and so that functions.invertQTransform fail.

one of the ways to work around it is mannually resize the Window by setFixedSize() or adjustSize().

For example, I post the cross hair example of pyqtgraph below for illustration. 
### Short description
<!-- This should summarize the issue. -->
GraphicsWindow.addPlot doesn't initialize correct ViewBox size, which lead to exception when use ViewBox.mapToView.
My tricky is just resize GraphicsWindow by adjustSize() method, and it does work.

### Code to reproduce

```
"""
Demonstrates some customized mouse interaction by drawing a crosshair that follows
the mouse.
"""

#import initExample ## Add path to library (just for examples; you do not need this)
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.Point import Point



#generate layout
app = QtGui.QApplication([])
#app = QtGui.QApplication()
#win = pg.GraphicsLayoutWidget(show=True)
#win = pg.GraphicsLayoutWidget(show=True, parent=None)

win = pg.GraphicsWindow()    # Using GraphicsWindow so can I run the file on PyCharm
win.setWindowTitle('pyqtgraph example: crosshair')

label = pg.LabelItem(justify='right')
win.addItem(label)

p1 = win.addPlot(row=1, col=0)
p2 = win.addPlot(row=2, col=0)

region = pg.LinearRegionItem()
region.setZValue(10)
#Add the LinearRegionItem to the ViewBox, but tell the ViewBox to exclude this
#item when doing auto-range calculations.

p2.addItem(region, ignoreBounds=True)

#pg.dbg()
'''
Set whether automatic range uses only visible data when determining the range to show.
'''
#p1.setAutoVisible(x=False, y=False)


#create numpy arrays
#make the numbers large to show that the xrange shows data from 10000 to all the way 0
data1 = 10000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
data2 = 15000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)

p1.plot(data1, pen="r")
p1.plot(data2, pen="g")

p2.plot(data1, pen="w")

'''*********The Issue*********'''
#win.adjustSize()    # my trick, please decomment the line and the program would work out

print(p1.getViewBox().size())               # ->PyQt5.QtCore.QSizeF()
print(p1.getViewBox().viewPixelSize())      # exception raised due to empty viewBox size
'''*********The Issue*********'''

def update():
    region.setZValue(10)
    minX, maxX = region.getRegion()
    p1.setXRange(minX, maxX, padding=0)


region.sigRegionChanged.connect(update)

def updateRegion(window, viewRange):
    rgn = viewRange[0]
    region.setRegion(rgn)


p1.sigRangeChanged.connect(updateRegion)



region.setRegion([1000, 2000])

#cross hair
vLine = pg.InfiniteLine(angle=90, movable=False)
hLine = pg.InfiniteLine(angle=0, movable=False)
p1.addItem(vLine, ignoreBounds=True)
p1.addItem(hLine, ignoreBounds=True)


vb = p1.vb




def mouseMoved(evt):
    #print(evt)

    pos = evt[0]  #using signal proxy turns original arguments into a tuple
    print(pos)

    if p1.sceneBoundingRect().contains(pos):
        mousePoint = vb.mapSceneToView(pos)
        print(mousePoint)
        index = int(mousePoint.x())
        if index > 0 and index < len(data1):
            label.setText("<span style='font-size: 12pt'>x=%0.1f,   <span style='color: red'>y1=%0.1f</span>,   <span style='color: green'>y2=%0.1f</span>" % (mousePoint.x(), data1[index], data2[index]))
        vLine.setPos(mousePoint.x())
        hLine.setPos(mousePoint.y())



proxy = pg.SignalProxy(p1.scene().sigMouseMoved, rateLimit=60, slot=mouseMoved)
#p1.scene().sigMouseMoved.connect(mouseMoved)

##Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()
```

### Expected behavior
p1.getViewBox().viewPixelSize() should work

### Real behavior
but it raises an exception

```
Traceback (most recent call last):
  File "C:/Users/Lee King Batchelor/Desktop/PythonExcel4Securities/pyqtgraph/CrossHair.py", line 86, in <module>
    print(p1.getViewBox().viewPixelSize())      # exception raised due to empty viewBox size
  File "C:\Anaconda3\envs\pyqtgraph\lib\site-packages\pyqtgraph\graphicsItems\ViewBox\ViewBox.py", line 1176, in viewPixelSize
    o = self.mapToView(Point(0,0))
  File "C:\Anaconda3\envs\pyqtgraph\lib\site-packages\pyqtgraph\graphicsItems\ViewBox\ViewBox.py", line 1142, in mapToView
    m = fn.invertQTransform(self.childTransform())
  File "C:\Anaconda3\envs\pyqtgraph\lib\site-packages\pyqtgraph\functions.py", line 2199, in invertQTransform
    inv = numpy.linalg.inv(arr)
  File "<__array_function__ internals>", line 6, in inv
  File "C:\Anaconda3\envs\pyqtgraph\lib\site-packages\numpy\linalg\linalg.py", line 547, in inv
    ainv = _umath_linalg.inv(a, signature=signature, extobj=extobj)
  File "C:\Anaconda3\envs\pyqtgraph\lib\site-packages\numpy\linalg\linalg.py", line 97, in _raise_linalgerror_singular
    raise LinAlgError("Singular matrix")
numpy.linalg.LinAlgError: Singular matrix
```

### Tested environment(s)

 * PyQtGraph version: 0.1 and 0.11 dev0
 * Qt Python binding:PyQt5.9.2 and PySide2 5.13.1
 * Python version: 3.7
 * NumPy version: 1.8.1
 * Operating system: Win 10
 * Installation method: pip and system package
### Additional context



Discussion:
Inserting an `app.processEvents()` after constructing the plot is another workaround. This allows events to propagate before the main event loop is executed, so initial bounding rects propagate and so on and the `ViewBox` can return a non-default size.

```python
import pyqtgraph as pg

app = pg.mkQApp()

win = pg.GraphicsLayoutWidget()
p = win.addPlot()
#app.processEvents() # uncomment to fix
p.getViewBox().viewPixelSize()

win.show()
app.exec_()
```

I think the values you get from `viewPixelSize` won't really be useful unless you call `app.processEvents` or wait until the window is actually shown anyway, but it'd probably be good to prevent the crash.

[EDIT] No longer valid, PR is closed.
@hf429 Thank you for the report. The fundamental problem is also reported in the very old issue #8. Could you please check whether Pull Request #1167 fixes the issue?

Resolving patch:
diff --git a/pyqtgraph/graphicsItems/GraphicsLayout.py b/pyqtgraph/graphicsItems/GraphicsLayout.py
--- a/pyqtgraph/graphicsItems/GraphicsLayout.py
+++ b/pyqtgraph/graphicsItems/GraphicsLayout.py
@@ -134,6 +134,9 @@ def addItem(self, item, row=None, col=None, rowspan=1, colspan=1):
         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):


Test patch: