Example #1
0
 def bend(self, amount):
     """Bend the line to the right by a given distance."""
     if amount == 0:
         self.arcthrupoint = None
         return self
     middle = self.p1.midpoint(self.p2)
     nx = (middle.y() - self.p1.y()) / abs(self.p1.distance(middle))
     ny = (self.p1.x() - middle.x()) / abs(self.p1.distance(middle))
     vx = middle.x() - self.p1.x()
     vy = middle.y() - self.p1.y()
     if (vx * ny - vy * nx) > 0:
         nx *= -1
         ny *= -1
     arcpoint = Point(middle.x() + amount * nx, middle.y() + amount * ny)
     if config.getOptions().VDEBUG:
         FeynDiagram.currenDiagram.currentCanvas.stroke(
             pyx.path.line(middle.x(), middle.y(), arcpoint.x(),
                           arcpoint.y()), [color.rgb.blue])
     self.arcThru(arcpoint)
     if config.getOptions().DEBUG:
         print(self.getVisiblePath())
     if config.getOptions().VDEBUG:
         FeynDiagram.currenDiagram.currentCanvas.stroke(
             self.getVisiblePath(), [color.rgb.blue])
     return self
Example #2
0
 def bend(self, amount):
     """Bend the line to the right by a given distance."""
     middle = self.p1.midpoint(self.p2)
     nx = (middle.y() - self.p1.y()) / abs(self.p1.distance(middle))
     ny = (self.p1.x() - middle.x()) / abs(self.p1.distance(middle))
     vx = middle.x() - self.p1.x()
     vy = middle.y() - self.p1.y()
     if (vx * ny - vy * nx) > 0:
         nx *= -1
         ny *= -1
     arcpoint = Point(middle.x() + amount * nx, middle.y() + amount * ny)
     if config.getOptions().VDEBUG:
         FeynDiagram.currenDiagram.currentCanvas.stroke(
             pyx.path.line(middle.x(), middle.y(), arcpoint.x(),
                           arcpoint.y()), [color.rgb.blue] )
     self.arcThru(arcpoint)
     if config.getOptions().DEBUG:
         print self.getVisiblePath()
     if config.getOptions().VDEBUG:
         FeynDiagram.currenDiagram.currentCanvas.stroke(self.getVisiblePath(), [color.rgb.blue])
     return self
Example #3
0
    def getPath(self):
        """Get the path taken by this line."""
        if self.arcthrupoint is None:
            ## This is a simple straight line
            return pyx.path.path( pyx.path.moveto( *(self.p1.getXY()) ),
                              pyx.path.lineto( *(self.p2.getXY()) ) )
        elif (self.p1.x() == self.p2.x() and self.p1.y() == self.p2.y()):
            ## This is a tadpole-type loop and needs special care;
            ## We shall assume that the arcthrupoint is meant to be
            ## the antipode of the basepoint
            arccenter = self.p1.midpoint(self.arcthrupoint)
            arcradius = self.p1.distance(self.arcthrupoint) / 2.0

            ## TODO Why does a circle work and an arc doesn't?
            cargs = (arccenter.x(), arccenter.y(), arcradius)
            circle = pyx.path.circle(*cargs)
            line = pyx.path.line( self.p1.x(), self.p1.y(), arccenter.x(), arccenter.y())
            if config.getOptions().VDEBUG:
                FeynDiagram.currenDiagram.currentCanvas.stroke(line, [color.rgb.green])
            ass, bs = circle.intersect(line)
            subpaths = circle.split(ass[0])
            cpath = subpaths[0]
            return cpath

            ## or, with an arc...
            arcangle1 = arccenter.arg(self.p1)
            arcangle2 = arccenter.arg(self.p1) + 360
            arcargs = (arccenter.x(), arccenter.y(), arcradius, arcangle1, arcangle2)
            return pyx.path.path( pyx.path.arc(*arcargs) )

        else:
            n13, n23 = None, None
            ## Work out line gradients
            try:
                n13 = (self.p1.y() - self.arcthrupoint.y()) / (self.p1.x() - self.arcthrupoint.x())
            except ZeroDivisionError:
                if config.getOptions().DEBUG:
                    print("Grad 1 diverges")
                n13 = 1e100

            try:
                n23 = (self.p2.y() - self.arcthrupoint.y()) / (self.p2.x() - self.arcthrupoint.x())
            except ZeroDivisionError:
                if config.getOptions().DEBUG:
                    print("Grad 2 diverges")
                n23 = 1e100

            ## If gradients match,
            ## then we have a straight line, so bypass the complexity
            if n13 == n23:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.lineto(*(self.p2.getXY())) )

            ## Otherwise work out conjugate gradients and midpoints
            m13, m23 = None, None
            try:
                m13 = -1.0 / n13
            except ZeroDivisionError:
                m13 = 1e100
            try:
                m23 = -1.0 / n23
            except ZeroDivisionError:
                m23 = 1e100
            mid13 = self.p1.midpoint(self.arcthrupoint)
            mid23 = self.p2.midpoint(self.arcthrupoint)

            ## Line y-intercepts
            c13 = mid13.y() - m13 * mid13.x()
            c23 = mid23.y() - m23 * mid23.x()

            ## Find the centre of the arc
            xcenter =  - (c23 - c13) / (m23 - m13)
            ycenter = m13 * xcenter + c13
            arccenter = Point(xcenter, ycenter)

            ## Get the angles required for drawing the arc
            arcradius = arccenter.distance(self.arcthrupoint)
            arcangle1 = arccenter.arg(self.p1)
            arcangle2 = arccenter.arg(self.p2)
            arcangle3 = arccenter.arg(self.arcthrupoint)
            arcargs = (arccenter.x(), arccenter.y(), arcradius, arcangle1, arcangle2)

            if config.getOptions().DEBUG and arcangle1 == arcangle2:
                print("Arc angles are the same - not drawing anything")

            ## Calculate cross product to determine direction of arc
            vec12 = [self.p2.x()-self.p1.x(), self.p2.y()-self.p1.y(), 0.0]
            vec13 = [self.arcthrupoint.x()-self.p1.x(), self.arcthrupoint.y()-self.p1.y(), 0.0]
            crossproductZcoord = vec12[0]*vec13[1] - vec12[1]*vec13[0]

            if crossproductZcoord < 0:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.arc(*arcargs))
            else:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.arcn(*arcargs))
Example #4
0
class Line(Visible):
    "Base class for all objects which connect points in Feynman diagrams"

    def __init__(self, point1, point2, styles=[], arcthrupoint=None, is3D=False, arrows=[], labels=[], **kwargs):
        """Constructor."""
        self.p1 = point1
        self.p2 = point2
        self.styles = styles
        self.arcthrupoint = arcthrupoint
        self.is3D = is3D
        self.arrows = arrows
        self.labels = labels

        ## Add this to the current diagram automatically
        FeynDiagram.currentDiagram.add(self)


    def addLabel(self, text, pos=0.5, displace=-0.25, angle = 0, size=pyx.text.size.normalsize, halign=CENTER, valign=None, **kwargs):
        """Add a LaTeX label to this line, either via parameters or actually as
        a TeXLabel object."""
        if config.getOptions().DEBUG:
            print("Adding label: " + text)
        #if text.__class__ == "Label":
        #    self.labels.append(label)
        #else:
        self.labels.append(LineLabel(text=text, line=self, pos=pos, displace=displace, angle=angle, size=size, halign=halign, valign=valign))
        if config.getOptions().DEBUG:
            print("Labels = " + str(self.labels))
        return self

    def addParallelArrow(self, pos=0.5, displace=0.3, length=0.5*pyx.unit.v_cm,
                 size=6*pyx.unit.v_pt, angle=45, constriction=0.8, sense=+1,
                 curved=False,stems=1,stemsep=0.03):
        """Add an arrow pointing along the line."""
        self.labels.append(ParallelArrow(self, pos=pos, displace=displace,
                                         length=length, size=size, angle=angle,
                                         constriction=constriction,sense=sense,
                                         curved=curved,stems=stems,
                                         stemsep=stemsep))
        return self


    def removeLabels(self):
        """Remove the labels from this line."""
        self.labels = []
        return self


    def fracpoint(self, frac):
        """
        Get a new Point representing the point at the given fraction along
        the fundamental line (i.e. no truncation or deformation).
        TODO: Handle units properly.
        """
        p = self.getPath() ## no truncation or deformation
        x, y = p.at(p.begin() + frac * p.arclen())
        return Point(x/defunit, y/defunit)


    def setArrows(self, arrows):
        """Define the arrows on this line."""
        ## TODO: Check that the arg is a list
        self.arrows = []
        for i in arrows:
            if i.__class__ == "deco.Arrow":
                self.arrows.append(i)
            else:
                self.arrows.append(Arrow(pos = i))
        return self


    def addArrow(self, position = 0.53, arrow = None):
        """Add an arrow to the line at the specified position, which is a number
        between 0 and 1, representing the fraction along the line at which the
        arrow should be placed. The default arrow style can be overridden by
        explicitly supplying an arrow object as the 'arrow' argument, in which
        case the position argument will be ignored."""
        if arrow:
            self.arrows.append(arrow)
        else:
            self.arrows.append(Arrow(pos=position))
        return self


    def removeArrows(self):
        """Remove all arrows from this line."""
        self.arrows = []
        return self


    def arcThru(self, arcpoint = None, x = None, y = None):
        """Set the point through which this line will arc. Either pass a Point
        or set x, y as floats."""
        if arcpoint is not None:
            self.arcthrupoint = arcpoint
        elif x is not None and y is not None:
            self.arcthrupoint = Point(x, y)
        else:
            raise Exception("Tried to set an arcpoint with invalid arguments")
        return self


    def straighten(self):
        """Make this line a straight line between start and end."""
        self.arcthrupoint = None
        return self


    def bend(self, amount):
        """Bend the line to the right by a given distance."""
        if amount==0:
           self.arcthrupoint = None
           return self
        middle = self.p1.midpoint(self.p2)
        nx = (middle.y() - self.p1.y()) / abs(self.p1.distance(middle))
        ny = (self.p1.x() - middle.x()) / abs(self.p1.distance(middle))
        vx = middle.x() - self.p1.x()
        vy = middle.y() - self.p1.y()
        if (vx * ny - vy * nx) > 0:
            nx *= -1
            ny *= -1
        arcpoint = Point(middle.x() + amount * nx, middle.y() + amount * ny)
        if config.getOptions().VDEBUG:
            FeynDiagram.currenDiagram.currentCanvas.stroke(
                pyx.path.line(middle.x(), middle.y(), arcpoint.x(),
                              arcpoint.y()), [color.rgb.blue] )
        self.arcThru(arcpoint)
        if config.getOptions().DEBUG:
            print(self.getVisiblePath())
        if config.getOptions().VDEBUG:
            FeynDiagram.currenDiagram.currentCanvas.stroke(self.getVisiblePath(), [color.rgb.blue])
        return self


    def set3D(self, choice):
        """Make this line display in '3D'."""
        self.is3D = choice
        return self


    def getStyles(self, stylelist):
        """Get the styles associated with this line."""
        return self.styles


    def setStyles(self, stylelist):
        """Set the styles associated with this line."""
        self.styles = stylelist
        return self


    def addStyle(self, style):
        """Add a style to this line."""
        self.styles.append(style)
        return self


    def addStyles(self, stylelist):
        """Add some styles to this line."""
        self.styles = self.styles + stylelist
        return self


    def getPath(self):
        """Get the path taken by this line."""
        if self.arcthrupoint is None:
            ## This is a simple straight line
            return pyx.path.path( pyx.path.moveto( *(self.p1.getXY()) ),
                              pyx.path.lineto( *(self.p2.getXY()) ) )
        elif (self.p1.x() == self.p2.x() and self.p1.y() == self.p2.y()):
            ## This is a tadpole-type loop and needs special care;
            ## We shall assume that the arcthrupoint is meant to be
            ## the antipode of the basepoint
            arccenter = self.p1.midpoint(self.arcthrupoint)
            arcradius = self.p1.distance(self.arcthrupoint) / 2.0

            ## TODO Why does a circle work and an arc doesn't?
            cargs = (arccenter.x(), arccenter.y(), arcradius)
            circle = pyx.path.circle(*cargs)
            line = pyx.path.line( self.p1.x(), self.p1.y(), arccenter.x(), arccenter.y())
            if config.getOptions().VDEBUG:
                FeynDiagram.currenDiagram.currentCanvas.stroke(line, [color.rgb.green])
            ass, bs = circle.intersect(line)
            subpaths = circle.split(ass[0])
            cpath = subpaths[0]
            return cpath

            ## or, with an arc...
            arcangle1 = arccenter.arg(self.p1)
            arcangle2 = arccenter.arg(self.p1) + 360
            arcargs = (arccenter.x(), arccenter.y(), arcradius, arcangle1, arcangle2)
            return pyx.path.path( pyx.path.arc(*arcargs) )

        else:
            n13, n23 = None, None
            ## Work out line gradients
            try:
                n13 = (self.p1.y() - self.arcthrupoint.y()) / (self.p1.x() - self.arcthrupoint.x())
            except ZeroDivisionError:
                if config.getOptions().DEBUG:
                    print("Grad 1 diverges")
                n13 = 1e100

            try:
                n23 = (self.p2.y() - self.arcthrupoint.y()) / (self.p2.x() - self.arcthrupoint.x())
            except ZeroDivisionError:
                if config.getOptions().DEBUG:
                    print("Grad 2 diverges")
                n23 = 1e100

            ## If gradients match,
            ## then we have a straight line, so bypass the complexity
            if n13 == n23:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.lineto(*(self.p2.getXY())) )

            ## Otherwise work out conjugate gradients and midpoints
            m13, m23 = None, None
            try:
                m13 = -1.0 / n13
            except ZeroDivisionError:
                m13 = 1e100
            try:
                m23 = -1.0 / n23
            except ZeroDivisionError:
                m23 = 1e100
            mid13 = self.p1.midpoint(self.arcthrupoint)
            mid23 = self.p2.midpoint(self.arcthrupoint)

            ## Line y-intercepts
            c13 = mid13.y() - m13 * mid13.x()
            c23 = mid23.y() - m23 * mid23.x()

            ## Find the centre of the arc
            xcenter =  - (c23 - c13) / (m23 - m13)
            ycenter = m13 * xcenter + c13
            arccenter = Point(xcenter, ycenter)

            ## Get the angles required for drawing the arc
            arcradius = arccenter.distance(self.arcthrupoint)
            arcangle1 = arccenter.arg(self.p1)
            arcangle2 = arccenter.arg(self.p2)
            arcangle3 = arccenter.arg(self.arcthrupoint)
            arcargs = (arccenter.x(), arccenter.y(), arcradius, arcangle1, arcangle2)

            if config.getOptions().DEBUG and arcangle1 == arcangle2:
                print("Arc angles are the same - not drawing anything")

            ## Calculate cross product to determine direction of arc
            vec12 = [self.p2.x()-self.p1.x(), self.p2.y()-self.p1.y(), 0.0]
            vec13 = [self.arcthrupoint.x()-self.p1.x(), self.arcthrupoint.y()-self.p1.y(), 0.0]
            crossproductZcoord = vec12[0]*vec13[1] - vec12[1]*vec13[0]

            if crossproductZcoord < 0:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.arc(*arcargs))
            else:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.arcn(*arcargs))


    def getVisiblePath(self):
        """Find the subpath between the endpoints which isn't overshadowed by a blob of some kind"""
        p1path = self.p1.getPath()
        p2path = self.p2.getPath()
        vispath = self.getPath()
        if config.getOptions().VDEBUG:
            FeynDiagram.currenDiagram.currentCanvas.stroke(vispath, [color.rgb.green])
        if p1path:
            ass, bs = p1path.intersect(vispath)
            for b in bs:
                subpaths = vispath.split(b)
                if len(subpaths) > 1:
                    if config.getOptions().DEBUG:
                        print("Num subpaths 1 = %d" % len(subpaths))
                    subpaths.sort(key=lambda x:pyx.unit.tocm(x.arclen()))    
                    vispath = subpaths[-1]
                    if config.getOptions().VDEBUG:
                        FeynDiagram.currenDiagram.currentCanvas.stroke(subpaths[0], [color.rgb.blue])
                if config.getOptions().VDEBUG:
                    for a in ass:
                        ix, iy = p1path.at(a)
                        FeynDiagram.currenDiagram.currentCanvas.fill(pyx.path.circle(ix, iy, 0.05),
                                                       [color.rgb.green])
        if p2path:
            ass, bs = p2path.intersect(vispath)
            for b in bs:
                subpaths = vispath.split(b)
                if len(subpaths) > 1:
                    if config.getOptions().DEBUG:
                        print("Num subpaths 2 = %d" % len(subpaths))
                    subpaths.sort(key=lambda x:pyx.unit.tocm(x.arclen()))    
                    vispath = subpaths[-1]
                    if config.getOptions().VDEBUG:
                        FeynDiagram.currenDiagram.currentCanvas.stroke(subpaths[0], [color.rgb.red])
                if config.getOptions().VDEBUG:
                    for a in ass:
                        ix, iy = p2path.at(a)
                        FeynDiagram.currenDiagram.currentCanvas.fill(pyx.path.circle(ix, iy, 0.05),
                                                       [color.rgb.blue])
        if config.getOptions().VDEBUG:
            FeynDiagram.currenDiagram.currentCanvas.stroke(vispath, [color.rgb.red])
        #return pyx.path.circle(-2,-1,0.2)
        return vispath


    def draw(self, canvas):
        """Drwa this line on the given canvas."""
        path = self.getVisiblePath()
        styles = self.styles + self.arrows
        if config.getOptions().DEBUG:
            print("Drawing " + str(self.__class__) + " with styles = " + str(styles))
            print(path)
        canvas.stroke(path, styles)
        for l in self.labels:
            l.draw(canvas)
Example #5
0
    def getPath(self):
        """Get the path taken by this line."""
        if self.arcthrupoint is None:
            ## This is a simple straight line
            return pyx.path.path( pyx.path.moveto( *(self.p1.getXY()) ),
                              pyx.path.lineto( *(self.p2.getXY()) ) )
        elif (self.p1.x() == self.p2.x() and self.p1.y() == self.p2.y()):
            ## This is a tadpole-type loop and needs special care;
            ## We shall assume that the arcthrupoint is meant to be
            ## the antipode of the basepoint
            arccenter = self.p1.midpoint(self.arcthrupoint)
            arcradius = self.p1.distance(self.arcthrupoint) / 2.0

            ## TODO Why does a circle work and an arc doesn't?
            cargs = (arccenter.x(), arccenter.y(), arcradius)
            circle = pyx.path.circle(*cargs)
            line = pyx.path.line( self.p1.x(), self.p1.y(), arccenter.x(), arccenter.y())
            if config.getOptions().VDEBUG:
                FeynDiagram.currenDiagram.currentCanvas.stroke(line, [color.rgb.green])
            ass, bs = circle.intersect(line)
            subpaths = circle.split(ass[0])
            cpath = subpaths[0]
            return cpath

            ## or, with an arc...
            arcangle1 = arccenter.arg(self.p1)
            arcangle2 = arccenter.arg(self.p1) + 360
            arcargs = (arccenter.x(), arccenter.y(), arcradius, arcangle1, arcangle2)
            return pyx.path.path( pyx.path.arc(*arcargs) )

        else:
            n13, n23 = None, None
            ## Work out line gradients
            try:
                n13 = (self.p1.y() - self.arcthrupoint.y()) / (self.p1.x() - self.arcthrupoint.x())
            except ZeroDivisionError:
                if config.getOptions().DEBUG:
                    print "Grad 1 diverges"
                n13 = 1e100

            try:
                n23 = (self.p2.y() - self.arcthrupoint.y()) / (self.p2.x() - self.arcthrupoint.x())
            except ZeroDivisionError:
                if config.getOptions().DEBUG:
                    print "Grad 2 diverges"
                n23 = 1e100

            ## If gradients match,
            ## then we have a straight line, so bypass the complexity
            if n13 == n23:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.lineto(*(self.p2.getXY())) )

            ## Otherwise work out conjugate gradients and midpoints
            m13, m23 = None, None
            try:
                m13 = -1.0 / n13
            except ZeroDivisionError:
                m13 = 1e100
            try:
                m23 = -1.0 / n23
            except ZeroDivisionError:
                m23 = 1e100
            mid13 = self.p1.midpoint(self.arcthrupoint)
            mid23 = self.p2.midpoint(self.arcthrupoint)

            ## Line y-intercepts
            c13 = mid13.y() - m13 * mid13.x()
            c23 = mid23.y() - m23 * mid23.x()

            ## Find the centre of the arc
            xcenter =  - (c23 - c13) / (m23 - m13)
            ycenter = m13 * xcenter + c13
            arccenter = Point(xcenter, ycenter)

            ## Get the angles required for drawing the arc
            arcradius = arccenter.distance(self.arcthrupoint)
            arcangle1 = arccenter.arg(self.p1)
            arcangle2 = arccenter.arg(self.p2)
            arcangle3 = arccenter.arg(self.arcthrupoint)
            arcargs = (arccenter.x(), arccenter.y(), arcradius, arcangle1, arcangle2)

            if config.getOptions().DEBUG and arcangle1 == arcangle2:
                print "Arc angles are the same - not drawing anything"

            ## Calculate cross product to determine direction of arc
            vec12 = [self.p2.x()-self.p1.x(), self.p2.y()-self.p1.y(), 0.0]
            vec13 = [self.arcthrupoint.x()-self.p1.x(), self.arcthrupoint.y()-self.p1.y(), 0.0]
            crossproductZcoord = vec12[0]*vec13[1] - vec12[1]*vec13[0]

            if crossproductZcoord < 0:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.arc(*arcargs))
            else:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.arcn(*arcargs))
Example #6
0
class Line(Visible):
    "Base class for all objects which connect points in Feynman diagrams"

    def __init__(self, point1, point2):
        """Constructor."""
        self.p1 = point1
        self.p2 = point2
        self.styles = []
        self.arcthrupoint = None
        self.is3D = False
        self.arrows = []
        self.labels = []

        ## Add this to the current diagram automatically
        FeynDiagram.currentDiagram.add(self)


    def addLabel(self, text, pos=0.5, displace=-0.25, angle = 0, size=pyx.text.size.normalsize):
        """Add a LaTeX label to this line, either via parameters or actually as
        a TeXLable object."""
        if config.getOptions().DEBUG:
            print "Adding label: " + text
        #if text.__class__ == "Label":
        #    self.labels.append(label)
        #else:
        self.labels.append(LineLabel(text=text, line=self, pos=pos, displace=displace, angle=angle, size=size))
        if config.getOptions().DEBUG:
            print "Labels = " + str(self.labels)
        return self

    def addParallelArrow(self, pos=0.5, displace=0.3, length=0.5*pyx.unit.v_cm,
                 size=6*pyx.unit.v_pt, angle=45, constriction=0.8, sense=+1,
                 curved=False,stems=1,stemsep=0.03, rotation=0):
        """Add an arrow pointing along the line."""
        self.labels.append(ParallelArrow(self, pos=pos, displace=displace,
                                         length=length, size=size, angle=angle,
                                         constriction=constriction,sense=sense,
                                         curved=curved,stems=stems,
                                         stemsep=stemsep, rotation=rotation))
        return self


    def removeLabels(self):
        """Remove the labels from this line."""
        self.labels = []
        return self


    def fracpoint(self, frac):
        """
        Get a new Point representing the point at the given fraction along
        the fundamental line (i.e. no truncation or deformation).
        TODO: Handle units properly.
        """
        p = self.getPath() ## no truncation or deformation
        x, y = p.at(p.begin() + frac * p.arclen())
        return Point(x/defunit, y/defunit)


    def setArrows(self, arrows):
        """Define the arrows on this line."""
        ## TODO: Check that the arg is a list
        self.arrows = []
        for i in arrows:
            if i.__class__ == "deco.Arrow":
                self.arrows.append(i)
            else:
                self.arrows.append(Arrow(pos = i))
        return self


    def addArrow(self, position = 0.53, arrow = None):
        """Add an arrow to the line at the specified position, which is a number
        between 0 and 1, representing the fraction along the line at which the
        arrow should be placed. The default arrow style can be overridden by
        explicitly supplying an arrow object as the 'arrow' argument, in which
        case the position argument will be ignored."""
        if arrow:
            self.arrows.append(arrow)
        else:
            self.arrows.append(Arrow(pos=position))
        return self


    def removeArrows(self):
        """Remove all arrows from this line."""
        self.arrows = []
        return self


    def arcThru(self, arcpoint = None, x = None, y = None):
        """Set the point through which this line will arc. Either pass a Point
        or set x, y as floats."""
        if arcpoint is not None:
            self.arcthrupoint = arcpoint
        elif x is not None and y is not None:
            self.arcthrupoint = Point(x, y)
        else:
            raise Exception("Tried to set an arcpoint with invalid arguments")
        return self


    def straighten(self):
        """Make this line a straight line between start and end."""
        self.arcthrupoint = None


    def bend(self, amount):
        """Bend the line to the right by a given distance."""
        middle = self.p1.midpoint(self.p2)
        nx = (middle.y() - self.p1.y()) / abs(self.p1.distance(middle))
        ny = (self.p1.x() - middle.x()) / abs(self.p1.distance(middle))
        vx = middle.x() - self.p1.x()
        vy = middle.y() - self.p1.y()
        if (vx * ny - vy * nx) > 0:
            nx *= -1
            ny *= -1
        arcpoint = Point(middle.x() + amount * nx, middle.y() + amount * ny)
        if config.getOptions().VDEBUG:
            FeynDiagram.currenDiagram.currentCanvas.stroke(
                pyx.path.line(middle.x(), middle.y(), arcpoint.x(),
                              arcpoint.y()), [color.rgb.blue] )
        self.arcThru(arcpoint)
        if config.getOptions().DEBUG:
            print self.getVisiblePath()
        if config.getOptions().VDEBUG:
            FeynDiagram.currenDiagram.currentCanvas.stroke(self.getVisiblePath(), [color.rgb.blue])
        return self


    def set3D(self, choice):
        """Make this line display in '3D'."""
        self.is3D = choice
        return self


    def getStyles(self, stylelist):
        """Get the styles associated with this line."""
        return self.styles


    def setStyles(self, stylelist):
        """Set the styles associated with this line."""
        self.styles = stylelist
        return self


    def addStyle(self, style):
        """Add a style to this line."""
        self.styles.append(style)
        return self


    def addStyles(self, stylelist):
        """Add some styles to this line."""
        self.styles = self.styles + stylelist
        return self


    def getPath(self):
        """Get the path taken by this line."""
        if self.arcthrupoint is None:
            ## This is a simple straight line
            return pyx.path.path( pyx.path.moveto( *(self.p1.getXY()) ),
                              pyx.path.lineto( *(self.p2.getXY()) ) )
        elif (self.p1.x() == self.p2.x() and self.p1.y() == self.p2.y()):
            ## This is a tadpole-type loop and needs special care;
            ## We shall assume that the arcthrupoint is meant to be
            ## the antipode of the basepoint
            arccenter = self.p1.midpoint(self.arcthrupoint)
            arcradius = self.p1.distance(self.arcthrupoint) / 2.0

            ## TODO Why does a circle work and an arc doesn't?
            cargs = (arccenter.x(), arccenter.y(), arcradius)
            circle = pyx.path.circle(*cargs)
            line = pyx.path.line( self.p1.x(), self.p1.y(), arccenter.x(), arccenter.y())
            if config.getOptions().VDEBUG:
                FeynDiagram.currenDiagram.currentCanvas.stroke(line, [color.rgb.green])
            ass, bs = circle.intersect(line)
            subpaths = circle.split(ass[0])
            cpath = subpaths[0]
            return cpath

            ## or, with an arc...
            arcangle1 = arccenter.arg(self.p1)
            arcangle2 = arccenter.arg(self.p1) + 360
            arcargs = (arccenter.x(), arccenter.y(), arcradius, arcangle1, arcangle2)
            return pyx.path.path( pyx.path.arc(*arcargs) )

        else:
            n13, n23 = None, None
            ## Work out line gradients
            try:
                n13 = (self.p1.y() - self.arcthrupoint.y()) / (self.p1.x() - self.arcthrupoint.x())
            except ZeroDivisionError:
                if config.getOptions().DEBUG:
                    print "Grad 1 diverges"
                n13 = 1e100

            try:
                n23 = (self.p2.y() - self.arcthrupoint.y()) / (self.p2.x() - self.arcthrupoint.x())
            except ZeroDivisionError:
                if config.getOptions().DEBUG:
                    print "Grad 2 diverges"
                n23 = 1e100

            ## If gradients match,
            ## then we have a straight line, so bypass the complexity
            if n13 == n23:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.lineto(*(self.p2.getXY())) )

            ## Otherwise work out conjugate gradients and midpoints
            m13, m23 = None, None
            try:
                m13 = -1.0 / n13
            except ZeroDivisionError:
                m13 = 1e100
            try:
                m23 = -1.0 / n23
            except ZeroDivisionError:
                m23 = 1e100
            mid13 = self.p1.midpoint(self.arcthrupoint)
            mid23 = self.p2.midpoint(self.arcthrupoint)

            ## Line y-intercepts
            c13 = mid13.y() - m13 * mid13.x()
            c23 = mid23.y() - m23 * mid23.x()

            ## Find the centre of the arc
            xcenter =  - (c23 - c13) / (m23 - m13)
            ycenter = m13 * xcenter + c13
            arccenter = Point(xcenter, ycenter)

            ## Get the angles required for drawing the arc
            arcradius = arccenter.distance(self.arcthrupoint)
            arcangle1 = arccenter.arg(self.p1)
            arcangle2 = arccenter.arg(self.p2)
            arcangle3 = arccenter.arg(self.arcthrupoint)
            arcargs = (arccenter.x(), arccenter.y(), arcradius, arcangle1, arcangle2)

            if config.getOptions().DEBUG and arcangle1 == arcangle2:
                print "Arc angles are the same - not drawing anything"

            ## Calculate cross product to determine direction of arc
            vec12 = [self.p2.x()-self.p1.x(), self.p2.y()-self.p1.y(), 0.0]
            vec13 = [self.arcthrupoint.x()-self.p1.x(), self.arcthrupoint.y()-self.p1.y(), 0.0]
            crossproductZcoord = vec12[0]*vec13[1] - vec12[1]*vec13[0]

            if crossproductZcoord < 0:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.arc(*arcargs))
            else:
                return pyx.path.path( pyx.path.moveto(*(self.p1.getXY())),
                                      pyx.path.arcn(*arcargs))


    def getVisiblePath(self):
        """Find the subpath between the endpoints which isn't overshadowed by a blob of some kind"""
        p1path = self.p1.getPath()
        p2path = self.p2.getPath()
        vispath = self.getPath()
        if config.getOptions().VDEBUG:
            FeynDiagram.currenDiagram.currentCanvas.stroke(vispath, [color.rgb.green])
        if p1path:
            ass, bs = p1path.intersect(vispath)
            for b in bs:
                subpaths = vispath.split(b)
                if len(subpaths) > 1:
                    if config.getOptions().DEBUG:
                        print "Num subpaths 1 = %d" % len(subpaths)
                    subpaths.sort(
                        lambda x, y :
                        int(pyx.unit.tocm(x.arclen() - y.arclen()) /
                            math.fabs(pyx.unit.tocm(x.arclen() - y.arclen()))) )
                    vispath = subpaths[-1]
                    if config.getOptions().VDEBUG:
                        FeynDiagram.currenDiagram.currentCanvas.stroke(subpaths[0], [color.rgb.blue])
                if config.getOptions().VDEBUG:
                    for a in ass:
                        ix, iy = p1path.at(a)
                        FeynDiagram.currenDiagram.currentCanvas.fill(pyx.path.circle(ix, iy, 0.05),
                                                       [color.rgb.green])
        if p2path:
            ass, bs = p2path.intersect(vispath)
            for b in bs:
                subpaths = vispath.split(b)
                if len(subpaths) > 1:
                    if config.getOptions().DEBUG:
                        print "Num subpaths 2 = %d" % len(subpaths)
                    subpaths.sort(
                        lambda x, y :
                        int(pyx.unit.tocm(x.arclen() - y.arclen()) /
                            math.fabs(pyx.unit.tocm(x.arclen() - y.arclen()))) )
                    vispath = subpaths[-1]
                    if config.getOptions().VDEBUG:
                        FeynDiagram.currenDiagram.currentCanvas.stroke(subpaths[0], [color.rgb.red])
                if config.getOptions().VDEBUG:
                    for a in ass:
                        ix, iy = p2path.at(a)
                        FeynDiagram.currenDiagram.currentCanvas.fill(pyx.path.circle(ix, iy, 0.05),
                                                       [color.rgb.blue])
        if config.getOptions().VDEBUG:
            FeynDiagram.currenDiagram.currentCanvas.stroke(vispath, [color.rgb.red])
        #return pyx.path.circle(-2,-1,0.2)
        return vispath


    def draw(self, canvas):
        """Drwa this line on the given canvas."""
        path = self.getVisiblePath()
        styles = self.styles + self.arrows
        if config.getOptions().DEBUG:
            print "Drawing " + str(self.__class__) + " with styles = " + str(styles)
            print path
        canvas.stroke(path, styles)
        for l in self.labels:
            l.draw(canvas)