Source code for pyfeyn2.render.pyx.points

"""Various types of points for vertices etc."""

import math
from copy import copy

import pyx

from pyfeyn2.render.pyx import config
from pyfeyn2.render.pyx.deco import PointLabel
from pyfeyn2.render.pyx.diagrams import FeynDiagram
from pyfeyn2.render.pyx.paint import BLACK
from pyfeyn2.render.pyx.utils import Visible


[docs]def midpoint(point1, point2): "Return the point midway between this point and the argument." return Point( (point1.getX() + point2.getX()) / 2.0, (point1.getY() + point2.getY()) / 2.0 )
[docs]def distance(point1, point2): "Calculate the distance between this point and the argument." return math.hypot(point1.x() - point2.x(), point1.y() - point2.y())
## Point base class
[docs]class Point: """Base class for all pointlike objects in Feynman diagrams."""
[docs] def __init__(self, x, y, blob=None, labels=None, **kwargs): """Constructor.""" if labels is None: labels = [] self.setXY(x, y) self.setBlob(blob) self.labels = labels
[docs] def addLabel(self, text, displace=0.3, angle=0, size=pyx.text.size.normalsize): """Add a LaTeX label to this point, either via parameters or actually as a PointLable object.""" if text is None: return self if config.getOptions().DEBUG: print("Adding label: " + text) self.labels.append( PointLabel(text=text, point=self, displace=displace, angle=angle, size=size) ) if config.getOptions().DEBUG: print("Labels = " + str(self.labels)) return self
[docs] def removeLabels(self): """Remove all labels from this point.""" self.labels = [] return self
[docs] def draw(self, canvas): "Do nothing (abstract base class)." pass
[docs] def getPath(self): "Return the path of the attached blob path, if there is one, otherwise None." if self.getBlob() and hasattr(self.getBlob(), "getPath"): return self.getBlob().getPath() else: return None
[docs] def midpoint(self, otherpoint): "Return the point midway between this point and the argument." return midpoint(self, otherpoint)
[docs] def distance(self, otherpoint): "Calculate the distance between this point and the argument." return distance(self, otherpoint)
[docs] def intercept(self, otherpoint): "Return the y-intercept of the straight line defined by this point and the argument." return self.y() - self.tangent(otherpoint) * self.x()
[docs] def tangent(self, otherpoint): "Return the tangent of the straight line defined by this point and the argument." if otherpoint.x() != self.x(): return (otherpoint.y() - self.y()) / (otherpoint.x() - self.x()) else: return float(10000) ## An arbitrary large number to replace infinity
[docs] def arg(self, otherpoint): """Return the angle between the x-axis and the straight line defined by this point and the argument (cf. complex numbers).""" arg = None if otherpoint.x() == self.x(): if otherpoint.y() > self.y(): arg = math.pi / 2.0 elif otherpoint.y() < self.y(): arg = ( 3 * math.pi / 2.0 ) # this will be reset to 0 if the points are the same if otherpoint.y() == self.y(): if otherpoint.x() < self.x(): arg = math.pi else: arg = 0.0 if otherpoint.x() != self.x() and otherpoint.y() != self.y(): arg = math.atan((otherpoint.y() - self.y()) / (otherpoint.x() - self.x())) if otherpoint.x() < self.x(): arg += math.pi elif otherpoint.y() < self.y(): arg += 2 * math.pi ## Convert to degrees argindegs = math.degrees(arg) return argindegs
[docs] def getBlob(self): "Get the attached blob." return self.blob
[docs] def setBlob(self, blob): "Set the attached blob." self.blob = blob return self
[docs] def getX(self): "Return the x-coordinate of this point." return self.xpos
[docs] def setX(self, x): "Set the x-coordinate of this point." self.xpos = x return self
[docs] def getY(self): "Return the y-coordinate of this point." return self.ypos
[docs] def setY(self, y): "Set the y-coordinate of this point." self.ypos = y return self
[docs] def getXY(self): "Return the x and y coordinates of this point as a 2-tuple." return self.getX(), self.getY()
[docs] def setXY(self, xpos, ypos): "Set the x and y coordinates of this point." if xpos is None or ypos is None: raise Exception("Point coordinates cannot be None") self.setX(float(xpos)) self.setY(float(ypos)) return self
[docs] def x(self): "Alias for getX()." return self.getX()
[docs] def y(self): "Alias for getY()." return self.getY()
[docs] def xy(self): "Alias for getXY()." return self.getXY()
## Decorated point class
[docs]class DecoratedPoint(Point, Visible): "Class for a point drawn with a marker"
[docs] def __init__( self, x=None, y=None, center=None, mark=None, fill=[BLACK], stroke=[BLACK], blob=None, labels=[], **kwargs ): """Constructor.""" xx = 0 yy = 0 if x is not None and y is not None: xx = x yy = y elif center is not None: xx = center.getX() yy = center.getY() else: raise Exception("No (x,y) or no center specified for blob.") Point.__init__(self, xx, yy, blob, labels) self.setMark(copy(mark)) self.layeroffset = 1000 self.fillstyles = copy(fill) # lists are mutable -- self.strokestyles = copy(stroke) # hence make a copy! ## Add this to the current diagram automatically FeynDiagram.currentDiagram.add(self)
[docs] def getPath(self): """Return the path belonging to the blob or marker attached to this point, if any.""" if self.blob: return self.getBlob().getPath() elif self.marker: return self.getMark().getPath() else: return None
[docs] def getMark(self): """Return the marker attached to this point.""" return self.marker
[docs] def setMark(self, mark): """Set the marker attached to this point.""" self.marker = mark if self.marker is not None: self.marker.setPoint(self) # if size is None and self.radius == 0: # change shape of a true point? # self.radius = 4*unit.t_pt # probably want to use default size return self
[docs] def getBlob(self): """Return the blob attached to this point.""" return self.blob
[docs] def setBlob(self, blob): """Set the blob attached to this point.""" self.blob = blob if self.blob is not None: self.blob.setPoints([self]) return self
[docs] def getFillstyles(self): """Return the fillstyles for the marker or blob attached to this point.""" return self.fillstyles
[docs] def setFillstyles(self, styles): """Set the fillstyles for the marker or blob attached to this point.""" self.fillstyles = styles return self
[docs] def addFillstyles(self, styles): """Add fillstyles to the marker or blob attached to this point.""" if styles is not None: self.fillstyles.add(styles) return self
[docs] def addFillstyle(self, style): """Add a fillstyle to the marker or blob attached to this point.""" self.fillstyles.append(style) return self
[docs] def getStrokestyles(self): """Return the stroke styles for the marker or blob attached to this point.""" return self.strokestyles
[docs] def setStrokestyles(self, styles): """Set the stroke styles for the marker or blob attached to this point.""" self.strokestyles = styles return self
[docs] def addStrokestyles(self, styles): """Add stroke styles to the marker or blob attached to this point.""" self.strokestyles.add(styles) return self
[docs] def addStrokestyle(self, style): """Add a stroke style to the marker or blob attached to this point.""" self.strokestyles.append(style) return self
[docs] def draw(self, canvas): """Draw the marker or blob attached to this point.""" if self.getPath(): canvas.fill(self.getPath(), self.fillstyles) canvas.stroke(self.getPath(), self.strokestyles) for l in self.labels: l.draw(canvas)
## Vertex is an alias for DecoratedPoint Vertex = DecoratedPoint
[docs]class Mark:
[docs] def getPoint(self): """Return the point to which this marker is attached.""" return self.point
[docs] def setPoint(self, point): """Attach this marker to a new point.""" self.point = point return self
[docs]class SquareMark(Mark):
[docs] def __init__(self, size=0.075): """A square mark.""" self.size = size self.point = None
[docs] def getPath(self): """Return the path for this marker.""" if self.getPoint() is not None: x, y = self.point.getXY() return pyx.box.rect( x - self.size, y - self.size, 2 * self.size, 2 * self.size ).path() return None
[docs]class CircleMark(Mark):
[docs] def __init__(self, size=0.075): """A circular mark.""" self.radius = size self.point = None
[docs] def getPath(self): """Return the path for this marker.""" if self.point is not None: x, y = self.point.getXY() return pyx.path.circle(x, y, self.radius).path() return None
[docs]class PolygonalMark(Mark):
[docs] def __init__(self, size=0.075, corners=3): """A polygonal mark.""" self.radius = size self.n = corners self.point = None
[docs] def getPath(self): """Return the path for this marker.""" if self.point is not None: x, y = self.point.getXY() return pyx.box.polygon( [ ( x - self.radius * math.sin(i * 2 * math.pi / self.n), y + self.radius * math.cos(i * 2 * math.pi / self.n), ) for i in range(self.n) ] ).path() return None
[docs]class StarshapeMark(Mark):
[docs] def __init__(self, size=0.075, raysize=0.05, rays=3): """A star-shaped mark.""" self.radius = size self.wiggle = raysize self.n = rays self.point = None
[docs] def getPath(self): """Return the path for this marker.""" if self.point is not None: x, y = self.point.getXY() return pyx.box.polygon( [ ( x - (self.radius - self.wiggle * (i % 2)) * math.sin(i * math.pi / self.n), y + (self.radius - self.wiggle * (i % 2)) * math.cos(i * math.pi / self.n), ) for i in range(2 * self.n) ] ).path() return None
[docs]class CrossMark(Mark):
[docs] def __init__(self, size=0.075): """A cross marker, e.g. to show neutrino oscillations.""" self.size = size self.point = None
[docs] def getPath(self): """Return the path for this marker.""" if self.point is not None: x, y = self.point.getXY() return pyx.path.path( pyx.path.moveto(x - self.size, y - self.size), pyx.path.lineto(x + self.size, y + self.size), pyx.path.moveto(x - self.size, y + self.size), pyx.path.lineto(x + self.size, y - self.size), ).path() return None
## Convenience constants CIRCLE = CircleMark() SQUARE = SquareMark() CROSS = CrossMark() TRIANGLE = PolygonalMark(corners=3) DIAMOND = PolygonalMark(corners=4) PENTAGON = PolygonalMark(corners=5) HEXAGON = PolygonalMark(corners=6) HEPTAGON = PolygonalMark(corners=7) OCTAGON = PolygonalMark(corners=8) TETRASTAR = StarshapeMark(rays=4) STAR = StarshapeMark(rays=5) HEXASTAR = StarshapeMark(rays=6) OCTOSTAR = StarshapeMark(rays=8) NamedMark = {"none": lambda size: None, "circle": CircleMark, "square": SquareMark}