229 lines
7.8 KiB
Python
229 lines
7.8 KiB
Python
#Copyright ReportLab Europe Ltd. 2000-2004
|
|
#see license.txt for license details
|
|
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/widgets/markers.py
|
|
"""
|
|
This modules defines a collection of markers used in charts.
|
|
"""
|
|
__version__=''' $Id$ '''
|
|
from types import FunctionType, ClassType
|
|
from reportlab.graphics.shapes import Rect, Line, Circle, Polygon, Drawing, Group
|
|
from reportlab.graphics.widgets.signsandsymbols import SmileyFace
|
|
from reportlab.graphics.widgetbase import Widget
|
|
from reportlab.lib.validators import isNumber, isColorOrNone, OneOf, Validator
|
|
from reportlab.lib.attrmap import AttrMap, AttrMapValue
|
|
from reportlab.lib.colors import black
|
|
from reportlab.graphics.widgets.flags import Flag
|
|
from math import sin, cos, pi
|
|
import copy, new
|
|
_toradians = pi/180.0
|
|
|
|
class Marker(Widget):
|
|
'''A polymorphic class of markers'''
|
|
_attrMap = AttrMap(BASE=Widget,
|
|
kind = AttrMapValue(
|
|
OneOf(None, 'Square', 'Diamond', 'Circle', 'Cross', 'Triangle', 'StarSix',
|
|
'Pentagon', 'Hexagon', 'Heptagon', 'Octagon', 'StarFive',
|
|
'FilledSquare', 'FilledCircle', 'FilledDiamond', 'FilledCross',
|
|
'FilledTriangle','FilledStarSix', 'FilledPentagon', 'FilledHexagon',
|
|
'FilledHeptagon', 'FilledOctagon', 'FilledStarFive',
|
|
'Smiley'),
|
|
desc='marker type name'),
|
|
size = AttrMapValue(isNumber,desc='marker size'),
|
|
x = AttrMapValue(isNumber,desc='marker x coordinate'),
|
|
y = AttrMapValue(isNumber,desc='marker y coordinate'),
|
|
dx = AttrMapValue(isNumber,desc='marker x coordinate adjustment'),
|
|
dy = AttrMapValue(isNumber,desc='marker y coordinate adjustment'),
|
|
angle = AttrMapValue(isNumber,desc='marker rotation'),
|
|
fillColor = AttrMapValue(isColorOrNone, desc='marker fill colour'),
|
|
strokeColor = AttrMapValue(isColorOrNone, desc='marker stroke colour'),
|
|
strokeWidth = AttrMapValue(isNumber, desc='marker stroke width'),
|
|
)
|
|
|
|
def __init__(self,*args,**kw):
|
|
self.kind = None
|
|
self.strokeColor = black
|
|
self.strokeWidth = 0.1
|
|
self.fillColor = None
|
|
self.size = 5
|
|
self.x = self.y = self.dx = self.dy = self.angle = 0
|
|
self.setProperties(kw)
|
|
|
|
def clone(self):
|
|
return new.instance(self.__class__,self.__dict__.copy())
|
|
|
|
def _Smiley(self):
|
|
x, y = self.x+self.dx, self.y+self.dy
|
|
d = self.size/2.0
|
|
s = SmileyFace()
|
|
s.fillColor = self.fillColor
|
|
s.strokeWidth = self.strokeWidth
|
|
s.strokeColor = self.strokeColor
|
|
s.x = x-d
|
|
s.y = y-d
|
|
s.size = d*2
|
|
return s
|
|
|
|
def _Square(self):
|
|
x, y = self.x+self.dx, self.y+self.dy
|
|
d = self.size/2.0
|
|
s = Rect(x-d,y-d,2*d,2*d,fillColor=self.fillColor,strokeColor=self.strokeColor,strokeWidth=self.strokeWidth)
|
|
return s
|
|
|
|
def _Diamond(self):
|
|
d = self.size/2.0
|
|
return self._doPolygon((-d,0,0,d,d,0,0,-d))
|
|
|
|
def _Circle(self):
|
|
x, y = self.x+self.dx, self.y+self.dy
|
|
s = Circle(x,y,self.size/2.0,fillColor=self.fillColor,strokeColor=self.strokeColor,strokeWidth=self.strokeWidth)
|
|
return s
|
|
|
|
def _Cross(self):
|
|
x, y = self.x+self.dx, self.y+self.dy
|
|
s = float(self.size)
|
|
h, s = s/2, s/6
|
|
return self._doPolygon((-s,-h,-s,-s,-h,-s,-h,s,-s,s,-s,h,s,h,s,s,h,s,h,-s,s,-s,s,-h))
|
|
|
|
def _Triangle(self):
|
|
x, y = self.x+self.dx, self.y+self.dy
|
|
r = float(self.size)/2
|
|
c = 30*_toradians
|
|
s = sin(30*_toradians)*r
|
|
c = cos(c)*r
|
|
return self._doPolygon((0,r,-c,-s,c,-s))
|
|
|
|
def _StarSix(self):
|
|
r = float(self.size)/2
|
|
c = 30*_toradians
|
|
s = sin(c)*r
|
|
c = cos(c)*r
|
|
z = s/2
|
|
g = c/2
|
|
return self._doPolygon((0,r,-z,s,-c,s,-s,0,-c,-s,-z,-s,0,-r,z,-s,c,-s,s,0,c,s,z,s))
|
|
|
|
def _StarFive(self):
|
|
R = float(self.size)/2
|
|
r = R*sin(18*_toradians)/cos(36*_toradians)
|
|
P = []
|
|
angle = 90
|
|
for i in xrange(5):
|
|
for radius in R, r:
|
|
theta = angle*_toradians
|
|
P.append(radius*cos(theta))
|
|
P.append(radius*sin(theta))
|
|
angle = angle + 36
|
|
return self._doPolygon(P)
|
|
|
|
def _Pentagon(self):
|
|
return self._doNgon(5)
|
|
|
|
def _Hexagon(self):
|
|
return self._doNgon(6)
|
|
|
|
def _Heptagon(self):
|
|
return self._doNgon(7)
|
|
|
|
def _Octagon(self):
|
|
return self._doNgon(8)
|
|
|
|
def _doPolygon(self,P):
|
|
x, y = self.x+self.dx, self.y+self.dy
|
|
if x or y: P = map(lambda i,P=P,A=[x,y]: P[i] + A[i&1], range(len(P)))
|
|
return Polygon(P, strokeWidth =self.strokeWidth, strokeColor=self.strokeColor, fillColor=self.fillColor)
|
|
|
|
def _doFill(self):
|
|
old = self.fillColor
|
|
if old is None:
|
|
self.fillColor = self.strokeColor
|
|
r = (self.kind and getattr(self,'_'+self.kind[6:]) or Group)()
|
|
self.fillColor = old
|
|
return r
|
|
|
|
def _doNgon(self,n):
|
|
P = []
|
|
size = float(self.size)/2
|
|
for i in xrange(n):
|
|
r = (2.*i/n+0.5)*pi
|
|
P.append(size*cos(r))
|
|
P.append(size*sin(r))
|
|
return self._doPolygon(P)
|
|
|
|
_FilledCircle = _doFill
|
|
_FilledSquare = _doFill
|
|
_FilledDiamond = _doFill
|
|
_FilledCross = _doFill
|
|
_FilledTriangle = _doFill
|
|
_FilledStarSix = _doFill
|
|
_FilledPentagon = _doFill
|
|
_FilledHexagon = _doFill
|
|
_FilledHeptagon = _doFill
|
|
_FilledOctagon = _doFill
|
|
_FilledStarFive = _doFill
|
|
|
|
def draw(self):
|
|
if self.kind:
|
|
m = getattr(self,'_'+self.kind)
|
|
if self.angle:
|
|
_x, _dx, _y, _dy = self.x, self.dx, self.y, self.dy
|
|
self.x, self.dx, self.y, self.dy = 0,0,0,0
|
|
try:
|
|
m = m()
|
|
finally:
|
|
self.x, self.dx, self.y, self.dy = _x, _dx, _y, _dy
|
|
if not isinstance(m,Group):
|
|
_m, m = m, Group()
|
|
m.add(_m)
|
|
if self.angle: m.rotate(self.angle)
|
|
x, y = _x+_dx, _y+_dy
|
|
if x or y: m.shift(x,y)
|
|
else:
|
|
m = m()
|
|
else:
|
|
m = Group()
|
|
return m
|
|
|
|
def uSymbol2Symbol(uSymbol,x,y,color):
|
|
if type(uSymbol) == FunctionType:
|
|
symbol = uSymbol(x, y, 5, color)
|
|
elif type(uSymbol) == ClassType and issubclass(uSymbol,Widget):
|
|
size = 10.
|
|
symbol = uSymbol()
|
|
symbol.x = x - (size/2)
|
|
symbol.y = y - (size/2)
|
|
try:
|
|
symbol.size = size
|
|
symbol.color = color
|
|
except:
|
|
pass
|
|
elif isinstance(uSymbol,Marker) or isinstance(uSymbol,Flag):
|
|
symbol = uSymbol.clone()
|
|
if isinstance(uSymbol,Marker): symbol.fillColor = symbol.fillColor or color
|
|
symbol.x, symbol.y = x, y
|
|
else:
|
|
symbol = None
|
|
return symbol
|
|
|
|
class _isSymbol(Validator):
|
|
def test(self,x):
|
|
return callable(x) or isinstance(x,Marker) or isinstance(x,Flag) \
|
|
or (type(x)==ClassType and issubclass(x,Widget))
|
|
|
|
isSymbol = _isSymbol()
|
|
|
|
def makeMarker(name,**kw):
|
|
if Marker._attrMap['kind'].validate(name):
|
|
m = apply(Marker,(),kw)
|
|
m.kind = name
|
|
elif name[-5:]=='_Flag' and Flag._attrMap['kind'].validate(name[:-5]):
|
|
m = apply(Flag,(),kw)
|
|
m.kind = name[:-5]
|
|
m.size = 10
|
|
else:
|
|
raise ValueError, "Invalid marker name %s" % name
|
|
return m
|
|
|
|
if __name__=='__main__':
|
|
D = Drawing()
|
|
D.add(Marker())
|
|
D.save(fnRoot='Marker',formats=['pdf'], outDir='/tmp')
|