2010-03-22 05:48:52 +00:00
# -*- coding: utf-8 -*-
"""
GraphicsView . py - Extension of QGraphicsView
Copyright 2010 Luke Campagnola
Distributed under MIT / X11 license . See license . txt for more infomation .
"""
2012-05-11 22:05:41 +00:00
from pyqtgraph . Qt import QtCore , QtGui
2012-06-29 18:39:27 +00:00
import pyqtgraph as pg
2012-05-11 22:05:41 +00:00
try :
from pyqtgraph . Qt import QtOpenGL
HAVE_OPENGL = True
except ImportError :
HAVE_OPENGL = False
2012-03-02 02:55:32 +00:00
from pyqtgraph . Point import Point
2011-06-14 23:47:52 +00:00
import sys , os
2012-05-11 22:05:41 +00:00
from . FileDialog import FileDialog
2012-03-02 02:55:32 +00:00
from pyqtgraph . GraphicsScene import GraphicsScene
import numpy as np
import pyqtgraph . functions as fn
2012-04-04 13:29:35 +00:00
import pyqtgraph . debug as debug
2012-03-27 16:30:51 +00:00
import pyqtgraph
2012-03-02 02:55:32 +00:00
__all__ = [ ' GraphicsView ' ]
2010-03-22 05:48:52 +00:00
class GraphicsView ( QtGui . QGraphicsView ) :
2012-06-29 18:39:27 +00:00
""" Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the
viewed coordinate range . Also automatically creates a GraphicsScene and a central QGraphicsWidget
that is automatically scaled to the full view geometry .
This widget is the basis for : class : ` PlotWidget < pyqtgraph . PlotWidget > ` ,
: class : ` GraphicsLayoutWidget < pyqtgraph . GraphicsLayoutWidget > ` , and the view widget in
: class : ` ImageView < pyqtgraph . ImageView > ` .
By default , the view coordinate system matches the widget ' s pixel coordinates and
automatically updates when the view is resized . This can be overridden by setting
autoPixelRange = False . The exact visible range can be set with setRange ( ) .
The view can be panned using the middle mouse button and scaled using the right mouse button if
enabled via enableMouse ( ) ( but ordinarily , we use ViewBox for this functionality ) . """
2011-04-05 14:35:50 +00:00
sigRangeChanged = QtCore . Signal ( object , object )
2012-10-22 17:34:03 +00:00
sigTransformChanged = QtCore . Signal ( object )
2011-04-05 14:35:50 +00:00
sigMouseReleased = QtCore . Signal ( object )
sigSceneMouseMoved = QtCore . Signal ( object )
#sigRegionChanged = QtCore.Signal(object)
2012-03-02 02:55:32 +00:00
sigScaleChanged = QtCore . Signal ( object )
2011-06-14 23:47:52 +00:00
lastFileDir = None
2011-04-05 14:35:50 +00:00
2012-06-29 18:39:27 +00:00
def __init__ ( self , parent = None , useOpenGL = None , background = ' default ' ) :
"""
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
Arguments :
parent Optional parent widget
useOpenGL If True , the GraphicsView will use OpenGL to do all of its
rendering . This can improve performance on some systems ,
but may also introduce bugs ( the combination of
QGraphicsView and QGLWidget is still an ' experimental '
feature of Qt )
background Set the background color of the GraphicsView . Accepts any
single argument accepted by
: func : ` mkColor < pyqtgraph . mkColor > ` . By
default , the background color is determined using the
' backgroundColor ' configuration option ( see
: func : ` setConfigOption < pyqtgraph . setConfigOption > ` .
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
"""
2010-03-22 05:48:52 +00:00
2011-04-05 14:35:50 +00:00
self . closed = False
2010-03-22 05:48:52 +00:00
2010-07-24 18:29:09 +00:00
QtGui . QGraphicsView . __init__ ( self , parent )
2012-03-02 02:55:32 +00:00
if useOpenGL is None :
2012-03-27 16:30:51 +00:00
useOpenGL = pyqtgraph . getConfigOption ( ' useOpenGL ' )
2010-07-24 18:29:09 +00:00
self . useOpenGL ( useOpenGL )
2011-06-14 23:47:52 +00:00
self . setCacheMode ( self . CacheBackground )
2012-05-11 03:37:07 +00:00
## This might help, but it's probably dangerous in the general case..
#self.setOptimizationFlag(self.DontSavePainterState, True)
2012-06-29 18:39:27 +00:00
self . setBackground ( background )
2011-06-14 23:47:52 +00:00
2010-03-22 05:48:52 +00:00
self . setFocusPolicy ( QtCore . Qt . StrongFocus )
self . setFrameShape ( QtGui . QFrame . NoFrame )
self . setVerticalScrollBarPolicy ( QtCore . Qt . ScrollBarAlwaysOff )
self . setHorizontalScrollBarPolicy ( QtCore . Qt . ScrollBarAlwaysOff )
self . setTransformationAnchor ( QtGui . QGraphicsView . NoAnchor )
self . setResizeAnchor ( QtGui . QGraphicsView . AnchorViewCenter )
2011-06-14 23:47:52 +00:00
self . setViewportUpdateMode ( QtGui . QGraphicsView . MinimalViewportUpdate )
2010-03-22 05:48:52 +00:00
self . lockedViewports = [ ]
self . lastMousePos = None
2012-03-02 02:55:32 +00:00
self . setMouseTracking ( True )
2010-03-22 05:48:52 +00:00
self . aspectLocked = False
self . range = QtCore . QRectF ( 0 , 0 , 1 , 1 )
self . autoPixelRange = True
self . currentItem = None
self . clearMouse ( )
self . updateMatrix ( )
2012-03-02 02:55:32 +00:00
self . sceneObj = GraphicsScene ( )
2010-03-22 05:48:52 +00:00
self . setScene ( self . sceneObj )
2011-02-08 00:40:38 +00:00
2012-07-03 18:44:07 +00:00
## Workaround for PySide crash
## This ensures that the scene will outlive the view.
if pyqtgraph . Qt . USE_PYSIDE :
self . sceneObj . _view_ref_workaround = self
2011-02-08 00:40:38 +00:00
## by default we set up a central widget with a grid layout.
## this can be replaced if needed.
2010-03-22 05:48:52 +00:00
self . centralWidget = None
self . setCentralItem ( QtGui . QGraphicsWidget ( ) )
2011-02-08 00:40:38 +00:00
self . centralLayout = QtGui . QGraphicsGridLayout ( )
self . centralWidget . setLayout ( self . centralLayout )
2010-03-22 05:48:52 +00:00
self . mouseEnabled = False
self . scaleCenter = False ## should scaling center around view center (True) or mouse click (False)
self . clickAccepted = False
2012-09-13 14:12:59 +00:00
def setAntialiasing ( self , aa ) :
""" Enable or disable default antialiasing.
Note that this will only affect items that do not specify their own antialiasing options . """
if aa :
self . setRenderHints ( self . renderHints ( ) | QtGui . QPainter . Antialiasing )
else :
self . setRenderHints ( self . renderHints ( ) & ~ QtGui . QPainter . Antialiasing )
2012-06-29 18:39:27 +00:00
def setBackground ( self , background ) :
"""
Set the background color of the GraphicsView .
To use the defaults specified py pyqtgraph . setConfigOption , use background = ' default ' .
To make the background transparent , use background = None .
"""
self . _background = background
if background == ' default ' :
background = pyqtgraph . getConfigOption ( ' background ' )
if background is None :
self . setBackgroundRole ( QtGui . QPalette . NoRole )
else :
brush = fn . mkBrush ( background )
self . setBackgroundBrush ( brush )
2011-04-05 14:35:50 +00:00
def close ( self ) :
2011-04-25 12:51:18 +00:00
self . centralWidget = None
self . scene ( ) . clear ( )
self . currentItem = None
self . sceneObj = None
2011-04-05 14:35:50 +00:00
self . closed = True
2011-06-14 23:47:52 +00:00
self . setViewport ( None )
2011-04-05 14:35:50 +00:00
2010-07-24 18:29:09 +00:00
def useOpenGL ( self , b = True ) :
if b :
2012-05-11 22:05:41 +00:00
if not HAVE_OPENGL :
raise Exception ( " Requested to use OpenGL with QGraphicsView, but QtOpenGL module is not available. " )
2010-07-24 18:29:09 +00:00
v = QtOpenGL . QGLWidget ( )
else :
v = QtGui . QWidget ( )
self . setViewport ( v )
def keyPressEvent ( self , ev ) :
2012-03-02 02:55:32 +00:00
self . scene ( ) . keyPressEvent ( ev ) ## bypass view, hand event directly to scene
## (view likes to eat arrow key events)
2010-07-24 18:29:09 +00:00
2010-03-22 05:48:52 +00:00
def setCentralItem ( self , item ) :
2012-03-02 02:55:32 +00:00
return self . setCentralWidget ( item )
def setCentralWidget ( self , item ) :
2012-06-29 18:39:27 +00:00
""" Sets a QGraphicsWidget to automatically fill the entire view (the item will be automatically
resize whenever the GraphicsView is resized ) . """
2010-03-22 05:48:52 +00:00
if self . centralWidget is not None :
self . scene ( ) . removeItem ( self . centralWidget )
self . centralWidget = item
self . sceneObj . addItem ( item )
2011-06-14 23:47:52 +00:00
self . resizeEvent ( None )
2010-03-22 05:48:52 +00:00
def addItem ( self , * args ) :
return self . scene ( ) . addItem ( * args )
2010-07-24 18:29:09 +00:00
def removeItem ( self , * args ) :
return self . scene ( ) . removeItem ( * args )
2010-03-22 05:48:52 +00:00
def enableMouse ( self , b = True ) :
self . mouseEnabled = b
self . autoPixelRange = ( not b )
def clearMouse ( self ) :
self . mouseTrail = [ ]
self . lastButtonReleased = None
def resizeEvent ( self , ev ) :
2011-04-05 14:35:50 +00:00
if self . closed :
return
2010-03-22 05:48:52 +00:00
if self . autoPixelRange :
self . range = QtCore . QRectF ( 0 , 0 , self . size ( ) . width ( ) , self . size ( ) . height ( ) )
2012-06-29 18:39:27 +00:00
GraphicsView . setRange ( self , self . range , padding = 0 , disableAutoPixel = False ) ## we do this because some subclasses like to redefine setRange in an incompatible way.
2010-03-22 05:48:52 +00:00
self . updateMatrix ( )
def updateMatrix ( self , propagate = True ) :
2011-06-14 23:47:52 +00:00
self . setSceneRect ( self . range )
2012-06-29 18:39:27 +00:00
if self . autoPixelRange :
self . resetTransform ( )
2010-03-22 05:48:52 +00:00
else :
2012-06-29 18:39:27 +00:00
if self . aspectLocked :
self . fitInView ( self . range , QtCore . Qt . KeepAspectRatio )
else :
self . fitInView ( self . range , QtCore . Qt . IgnoreAspectRatio )
2010-03-22 05:48:52 +00:00
2011-04-05 14:35:50 +00:00
self . sigRangeChanged . emit ( self , self . range )
2012-10-22 17:34:03 +00:00
self . sigTransformChanged . emit ( self )
2010-03-22 05:48:52 +00:00
if propagate :
for v in self . lockedViewports :
v . setXRange ( self . range , padding = 0 )
2012-03-02 02:55:32 +00:00
def viewRect ( self ) :
2010-07-24 18:29:09 +00:00
""" Return the boundaries of the view in scene coordinates """
## easier to just return self.range ?
2010-03-22 05:48:52 +00:00
r = QtCore . QRectF ( self . rect ( ) )
return self . viewportTransform ( ) . inverted ( ) [ 0 ] . mapRect ( r )
2012-03-02 02:55:32 +00:00
def visibleRange ( self ) :
## for backward compatibility
return self . viewRect ( )
2010-03-22 05:48:52 +00:00
def translate ( self , dx , dy ) :
self . range . adjust ( dx , dy , dx , dy )
self . updateMatrix ( )
def scale ( self , sx , sy , center = None ) :
scale = [ sx , sy ]
if self . aspectLocked :
scale [ 0 ] = scale [ 1 ]
if self . scaleCenter :
center = None
if center is None :
center = self . range . center ( )
w = self . range . width ( ) / scale [ 0 ]
h = self . range . height ( ) / scale [ 1 ]
self . range = QtCore . QRectF ( center . x ( ) - ( center . x ( ) - self . range . left ( ) ) / scale [ 0 ] , center . y ( ) - ( center . y ( ) - self . range . top ( ) ) / scale [ 1 ] , w , h )
self . updateMatrix ( )
2012-03-02 02:55:32 +00:00
self . sigScaleChanged . emit ( self )
2010-03-22 05:48:52 +00:00
def setRange ( self , newRect = None , padding = 0.05 , lockAspect = None , propagate = True , disableAutoPixel = True ) :
if disableAutoPixel :
self . autoPixelRange = False
if newRect is None :
newRect = self . visibleRange ( )
padding = 0
2012-03-02 02:55:32 +00:00
2010-03-22 05:48:52 +00:00
padding = Point ( padding )
newRect = QtCore . QRectF ( newRect )
pw = newRect . width ( ) * padding [ 0 ]
ph = newRect . height ( ) * padding [ 1 ]
2012-03-02 02:55:32 +00:00
newRect = newRect . adjusted ( - pw , - ph , pw , ph )
scaleChanged = False
if self . range . width ( ) != newRect . width ( ) or self . range . height ( ) != newRect . height ( ) :
scaleChanged = True
self . range = newRect
2010-03-22 05:48:52 +00:00
#print "New Range:", self.range
self . centralWidget . setGeometry ( self . range )
self . updateMatrix ( propagate )
2012-03-02 02:55:32 +00:00
if scaleChanged :
self . sigScaleChanged . emit ( self )
2011-04-05 14:35:50 +00:00
def scaleToImage ( self , image ) :
""" Scales such that pixels in image are the same size as screen pixels. This may result in a significant performance increase. """
pxSize = image . pixelSize ( )
2012-03-02 02:55:32 +00:00
image . setPxMode ( True )
try :
self . sigScaleChanged . disconnect ( image . setScaledMode )
except TypeError :
pass
2011-04-05 14:35:50 +00:00
tl = image . sceneBoundingRect ( ) . topLeft ( )
w = self . size ( ) . width ( ) * pxSize [ 0 ]
h = self . size ( ) . height ( ) * pxSize [ 1 ]
range = QtCore . QRectF ( tl . x ( ) , tl . y ( ) , w , h )
2012-03-12 16:23:25 +00:00
GraphicsView . setRange ( self , range , padding = 0 )
2012-03-02 02:55:32 +00:00
self . sigScaleChanged . connect ( image . setScaledMode )
2011-04-05 14:35:50 +00:00
2010-03-22 05:48:52 +00:00
def lockXRange ( self , v1 ) :
if not v1 in self . lockedViewports :
self . lockedViewports . append ( v1 )
def setXRange ( self , r , padding = 0.05 ) :
r1 = QtCore . QRectF ( self . range )
r1 . setLeft ( r . left ( ) )
r1 . setRight ( r . right ( ) )
2012-03-12 16:23:25 +00:00
GraphicsView . setRange ( self , r1 , padding = [ padding , 0 ] , propagate = False )
2010-03-22 05:48:52 +00:00
def setYRange ( self , r , padding = 0.05 ) :
r1 = QtCore . QRectF ( self . range )
r1 . setTop ( r . top ( ) )
r1 . setBottom ( r . bottom ( ) )
2012-03-12 16:23:25 +00:00
GraphicsView . setRange ( self , r1 , padding = [ 0 , padding ] , propagate = False )
2010-03-22 05:48:52 +00:00
def wheelEvent ( self , ev ) :
2011-02-03 10:49:56 +00:00
QtGui . QGraphicsView . wheelEvent ( self , ev )
2010-03-22 05:48:52 +00:00
if not self . mouseEnabled :
return
sc = 1.001 * * ev . delta ( )
#self.scale *= sc
#self.updateMatrix()
self . scale ( sc , sc )
def setAspectLocked ( self , s ) :
self . aspectLocked = s
2012-03-02 02:55:32 +00:00
def leaveEvent ( self , ev ) :
self . scene ( ) . leaveEvent ( ev ) ## inform scene when mouse leaves
2010-03-22 05:48:52 +00:00
def mousePressEvent ( self , ev ) :
QtGui . QGraphicsView . mousePressEvent ( self , ev )
2010-05-01 16:19:27 +00:00
2010-03-22 05:48:52 +00:00
if not self . mouseEnabled :
return
self . lastMousePos = Point ( ev . pos ( ) )
self . mousePressPos = ev . pos ( )
self . clickAccepted = ev . isAccepted ( )
if not self . clickAccepted :
self . scene ( ) . clearSelection ( )
return ## Everything below disabled for now..
def mouseReleaseEvent ( self , ev ) :
QtGui . QGraphicsView . mouseReleaseEvent ( self , ev )
if not self . mouseEnabled :
return
2011-04-05 14:35:50 +00:00
self . sigMouseReleased . emit ( ev )
2010-03-22 05:48:52 +00:00
self . lastButtonReleased = ev . button ( )
return ## Everything below disabled for now..
def mouseMoveEvent ( self , ev ) :
2011-02-10 03:44:09 +00:00
if self . lastMousePos is None :
self . lastMousePos = Point ( ev . pos ( ) )
2011-06-14 23:47:52 +00:00
delta = Point ( ev . pos ( ) - self . lastMousePos )
2011-02-10 03:44:09 +00:00
self . lastMousePos = Point ( ev . pos ( ) )
2010-03-22 05:48:52 +00:00
QtGui . QGraphicsView . mouseMoveEvent ( self , ev )
if not self . mouseEnabled :
return
2011-04-05 14:35:50 +00:00
self . sigSceneMouseMoved . emit ( self . mapToScene ( ev . pos ( ) ) )
2010-03-22 05:48:52 +00:00
if self . clickAccepted : ## Ignore event if an item in the scene has already claimed it.
return
if ev . buttons ( ) == QtCore . Qt . RightButton :
2012-03-02 02:55:32 +00:00
delta = Point ( np . clip ( delta [ 0 ] , - 50 , 50 ) , np . clip ( - delta [ 1 ] , - 50 , 50 ) )
2010-03-22 05:48:52 +00:00
scale = 1.01 * * delta
self . scale ( scale [ 0 ] , scale [ 1 ] , center = self . mapToScene ( self . mousePressPos ) )
2011-04-05 14:35:50 +00:00
self . sigRangeChanged . emit ( self , self . range )
2010-03-22 05:48:52 +00:00
elif ev . buttons ( ) in [ QtCore . Qt . MidButton , QtCore . Qt . LeftButton ] : ## Allow panning by left or mid button.
2011-06-14 23:47:52 +00:00
px = self . pixelSize ( )
tr = - delta * px
2010-03-22 05:48:52 +00:00
self . translate ( tr [ 0 ] , tr [ 1 ] )
2011-04-05 14:35:50 +00:00
self . sigRangeChanged . emit ( self , self . range )
2010-03-22 05:48:52 +00:00
2011-06-14 23:47:52 +00:00
def pixelSize ( self ) :
""" Return vector with the length and width of one view pixel in scene coordinates """
p0 = Point ( 0 , 0 )
p1 = Point ( 1 , 1 )
tr = self . transform ( ) . inverted ( ) [ 0 ]
p01 = tr . map ( p0 )
p11 = tr . map ( p1 )
return Point ( p11 - p01 )
2011-02-08 00:40:38 +00:00
def dragEnterEvent ( self , ev ) :
ev . ignore ( ) ## not sure why, but for some reason this class likes to consume drag events
2010-03-22 05:48:52 +00:00