ArrowItem.py 4.38 KB
Newer Older
1 2
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph.functions as fn
3
import numpy as np
4
__all__ = ['ArrowItem']
5 6

class ArrowItem(QtGui.QGraphicsPathItem):
7 8 9 10 11 12 13 14
    """
    For displaying scale-invariant arrows.
    For arrows pointing to a location on a curve, see CurveArrow
    
    """
    
    
    def __init__(self, **opts):
15 16 17 18
        """
        Arrows can be initialized with any keyword arguments accepted by 
        the setStyle() method.
        """
19
        QtGui.QGraphicsPathItem.__init__(self, opts.get('parent', None))
20 21 22 23
        if 'size' in opts:
            opts['headLen'] = opts['size']
        if 'width' in opts:
            opts['headWidth'] = opts['width']
24 25
        defOpts = {
            'pxMode': True,
Luke Campagnola's avatar
Luke Campagnola committed
26
            'angle': -150,   ## If the angle is 0, the arrow points left
27
            'pos': (0,0),
28
            'headLen': 20,
29
            'tipAngle': 25,
30 31 32
            'baseAngle': 0,
            'tailLen': None,
            'tailWidth': 3,
33 34 35 36 37 38 39 40 41 42 43 44 45 46
            'pen': (200,200,200),
            'brush': (50,50,200),
        }
        defOpts.update(opts)
        
        self.setStyle(**defOpts)
        
        self.setPen(fn.mkPen(defOpts['pen']))
        self.setBrush(fn.mkBrush(defOpts['brush']))
        
        self.rotate(self.opts['angle'])
        self.moveBy(*self.opts['pos'])
    
    def setStyle(self, **opts):
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
        """
        Changes the appearance of the arrow.
        All arguments are optional:
        
        ================= =================================================
        Keyword Arguments
        angle             Orientation of the arrow in degrees. Default is
                          0; arrow pointing to the left.
        headLen           Length of the arrow head, from tip to base.
                          default=20
        headWidth         Width of the arrow head at its base.
        tipAngle          Angle of the tip of the arrow in degrees. Smaller
                          values make a 'sharper' arrow. If tipAngle is 
                          specified, ot overrides headWidth. default=25
        baseAngle         Angle of the base of the arrow head. Default is
                          0, which means that the base of the arrow head
                          is perpendicular to the arrow shaft.
        tailLen           Length of the arrow tail, measured from the base
                          of the arrow head to the tip of the tail. If
                          this value is None, no tail will be drawn.
                          default=None
        tailWidth         Width of the tail. default=3
        pen               The pen used to draw the outline of the arrow.
        brush             The brush used to fill the arrow.
        ================= =================================================
        """
73 74
        self.opts = opts
        
75
        opt = dict([(k,self.opts[k]) for k in ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth']])
76 77
        self.path = fn.makeArrowPath(**opt)
        self.setPath(self.path)
78 79 80 81 82 83 84 85
        
        if opts['pxMode']:
            self.setFlags(self.flags() | self.ItemIgnoresTransformations)
        else:
            self.setFlags(self.flags() & ~self.ItemIgnoresTransformations)
        
    def paint(self, p, *args):
        p.setRenderHint(QtGui.QPainter.Antialiasing)
86
        QtGui.QGraphicsPathItem.paint(self, p, *args)
Luke Campagnola's avatar
Luke Campagnola committed
87 88 89 90
        
        #p.setPen(fn.mkPen('r'))
        #p.setBrush(fn.mkBrush(None))
        #p.drawRect(self.boundingRect())
91 92 93 94

    def shape(self):
        #if not self.opts['pxMode']:
            #return QtGui.QGraphicsPathItem.shape(self)
Luke Campagnola's avatar
Luke Campagnola committed
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
        return self.path
    
    ## dataBounds and pixelPadding methods are provided to ensure ViewBox can
    ## properly auto-range 
    def dataBounds(self, ax, frac, orthoRange=None):
        pw = 0
        pen = self.pen()
        if not pen.isCosmetic():
            pw = pen.width() * 0.7072
        if self.opts['pxMode']:
            return [0,0]
        else:
            br = self.boundingRect()
            if ax == 0:
                return [br.left()-pw, br.right()+pw]
            else:
                return [br.top()-pw, br.bottom()+pw]
        
    def pixelPadding(self):
        pad = 0
        if self.opts['pxMode']:
            br = self.boundingRect()
            pad += (br.width()**2 + br.height()**2) ** 0.5
        pen = self.pen()
        if pen.isCosmetic():
            pad += max(1, pen.width()) * 0.7072
        return pad