# Copyright (C) 2002-2006 Alexei Gilchrist and Paul Cochrane
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# $Id: arrowheads.py,v 1.6 2006/05/15 11:52:15 paultcochrane Exp $
"""
Arrow heads for Path and elsewhere
"""
__revision__ = '$Revision: 1.6 $'
from pyscript.defaults import defaults
#from math import sqrt, pi, sin, cos
from pyscript.vectors import P,Bbox# ,Identity
frompyscript.baseColor
from pyscript.objects import AffineObj
import cStringIO
# -------------------------------------------------------------------------
# Base class: ArrowHead
# -------------------------------------------------------------------------
class ArrowHead(AffineObj):
'''
Arrow head object
@cvar tip: where to position the tip of the arrow head
@cvar angle: the direction to point
Convenience variables modifying the head size:
@cvar scalew: scale the width by this amount
@cvar scaleh: scale height by this amount
The actual shape of the arrowhead is defined by the following, distances
are given in points
@cvar start: tuple giving starting point for path
@cvar shape: list of tuples giving arguments to postscripts curveto operator
@cvar closed: whether to close the path or not
@cvar fg: line color or None for no line
@cvar bg: fill color or None for no fill
@cvar linewidth: linewidth
@cvar linejoin: 0=miter, 1=round, 2=bevel
@cvar mitrelimit: length of mitre of corners
'''
fg = Color(0)
bg = Color(0)
# used by Path object to set position and direction
reverse = 0
pos = 1
tip = P(0, 0)
angle = 0
start = (0, 0)
# triangular share in the Golden ratio
# positions in pixels
shape = [(0, 0, 1.5, -4.854, 1.5, -4.854),
(1.5, -4.854, -1.5, -4.854, -1.5, -4.854),
(-1.5, -4.854, 0, 0, 0, 0)]
closed = 1
scalew = 1
scaleh = 1
linewidth = 0.2
linejoin = 2 #0=miter, 1=round, 2=bevel
# miterlimit:
# 1.414 cuts off miters at angles less than 90 degrees.
# 2.0 cuts off miters at angles less than 60 degrees.
# 10.0 cuts off miters at angles less than 11 degrees.
# 1.0 cuts off miters at all angles, so that bevels are always produced
miterlimit = 2
def __init__(self, *param, **options):
# remember this angle for when instance is copied...
# this assumes all rotations that have been applied
# are represented by angle and reverse
angle0 = self.angle + self.reverse*180
AffineObj.__init__(self, **options)
if len(param) == 1:
self.pos = param[0]
sx = self.scalew
sy = self.scaleh
self.start = (self.start[0]*sx, self.start[1]*sy)
shape = []
for b in self.shape:
shape.append((b[0]*sx, b[1]*sy, b[2]*sx, b[3]*sy, b[4]*sx, b[5]*sy))
self.shape = shape
self.rotate(self.angle+self.reverse*180-angle0)
self.move(self.tip)
def body(self):
"""
Return the postscript body of the Path
"""
out = cStringIO.StringIO()
if self.linewidth is not None:
out.write("%g setlinewidth " % self.linewidth)
if self.linejoin is not None:
out.write("%d setlinejoin " % self.linejoin)
if self.miterlimit is not None:
out.write("%f setmiterlimit " % self.miterlimit)
out.write('newpath %g %g moveto\n' % self.start)
for bez in self.shape:
out.write('%g %g %g %g %g %g curveto\n' % bez)
if self.closed:
out.write('closepath\n')
if self.bg is not None:
out.write("gsave %s fill grestore\n" % self.bg)
if self.fg is not None:
out.write("%s stroke\n" % self.fg)
return out.getvalue()
def bbox(self):
"""
Return the bounding box of the Path
"""
# the (0,0) point:
p0 = self.itoe(P(0, 0))
xmax = xmin = p0.x
ymax = ymin = p0.y
for bez in self.shape:
c1x, c1y, c2x, c2y, p2x, p2y = bez
p1 = self.itoe(P(c1x, c1y)/float(defaults.units))
p2 = self.itoe(P(c2x, c2y)/float(defaults.units))
p3 = self.itoe(P(p2x, p2y)/float(defaults.units))
xmax = max(xmax, p1.x, p2.x, p3.x)
xmin = min(xmin, p1.x, p2.x, p3.x)
ymax = max(ymax, p1.y, p2.y, p3.y)
ymin = min(ymin, p1.y, p2.y, p3.y)
return Bbox(sw=P(xmin, ymin), width=xmax-xmin, height=ymax-ymin)
# -------------------------------------------------------------------------
# Modifications
# -------------------------------------------------------------------------
# for symmetry....
class ArrowHead1(ArrowHead):
'''
Default Arrow head: triangular in Golden ratio
'''
pass
class ArrowHead2(ArrowHead):
'''
Similar to default but with concave base
'''
shape = [(0, 0, 1.5, -4.854, 1.5, -4.854),
(0, -2, 0, -2, -1.5, -4.854),
(-1.5, -4.854, 0, 0, 0, 0)]
class ArrowHead3(ArrowHead):
'''
Like default but rounded base
'''
shape = [(0, 0, 1.5, -4.854, 1.5, -4.854),
(1.5*1.5, 1.5*(-4.854), 1.5*(-1.5), 1.5*(-4.854), -1.5, -4.854),
(-1.5, -4.854, 0, 0, 0, 0)]
class ArrowHead4(ArrowHead):
'''
traditional line arrow
'''
bg = None
start = (2, -5)
closed = 0
linewidth = .5
shape = [(2, -5, 0, 0, 0, 0),
(0, 0, -2, -5, -2, -5)]
# vim: expandtab shiftwidth=4:
|