···
+
# TODO: Support drawing ellipses without manually using Path
+
''' Base class for drawing elements
+
Subclasses must implement writeSvgElement '''
+
def writeSvgElement(self, outputFile):
+
raise NotImplementedError('Abstract base class')
+
def __eq__(self, other):
+
class DrawingBasicElement(DrawingElement):
+
''' Base class for SVG drawing elements that are a single node with no
+
def __init__(self, **args):
+
def writeSvgElement(self, outputFile):
+
outputFile.write(self.TAG_NAME)
+
for k, v in self.args.items():
+
k = k.replace('_', '-')
+
outputFile.write('{}="{}" '.format(k,v))
+
def __eq__(self, other):
+
if isinstance(other, type(self)):
+
return (self.tagName == other.tagName and
+
self.args == other.args)
+
class NoElement(DrawingElement):
+
''' A drawing element that has no effect '''
+
def __init__(self): pass
+
def writeSvgElement(self, outputFile):
+
def __eq__(self, other):
+
if isinstance(other, type(self)):
+
class Rectangle(DrawingBasicElement):
+
Additional keyword arguments are ouput as additional arguments to the
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
+
def __init__(self, x, y, width, height, **kwargs):
+
super().__init__(x=x, y=-y-height, width=width, height=height,
+
class Circle(DrawingBasicElement):
+
Additional keyword arguments are ouput as additional properties to the
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
+
def __init__(self, cx, cy, r, **kwargs):
+
super().__init__(cx=cx, cy=-cy, r=r, **kwargs)
+
In most cases, use Arc instead of ArcLine. ArcLine uses the
+
stroke-dasharray SVG property to make the edge of a circle look like
+
Additional keyword arguments are ouput as additional arguments to the
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
+
def __init__(self, cx, cy, r, startDeg, endDeg, **kwargs):
+
if endDeg - startDeg == 360:
+
super().__init__(cx, cy, r, **kwargs)
+
startDeg, endDeg = (-endDeg) % 360, (-startDeg) % 360
+
arcDeg = (endDeg - startDeg) % 360
+
def arcLen(deg): return math.radians(deg) * r
+
wholeLen = 2 * math.pi * r
+
dashes = "0 {}".format(wholeLen+2)
+
#elif endDeg >= startDeg:
+
startLen = arcLen(startDeg)
+
arcLen = arcLen(arcDeg)
+
offLen = wholeLen - arcLen
+
dashes = "{} {}".format(arcLen, offLen)
+
# firstLen = arcLen(endDeg)
+
# secondLen = arcLen(360-startDeg)
+
# gapLen = wholeLen - firstLen - secondLen
+
# dashes = "{} {} {}".format(firstLen, gapLen, secondLen)
+
super().__init__(cx, cy, r, stroke_dasharray=dashes,
+
stroke_dashoffset=offset, **kwargs)
+
class Path(DrawingBasicElement):
+
Path Supports building an SVG path by calling instance methods
+
corresponding to path commands.
+
Additional keyword arguments are ouput as additional properties to the
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
+
def __init__(self, d='', **kwargs):
+
super().__init__(d=d, **kwargs)
+
def append(self, commandStr, *args):
+
if len(self.args['d']) > 0:
+
commandStr = ' ' + commandStr
+
commandStr = commandStr + ','.join(map(str, args))
+
self.args['d'] += commandStr
+
def M(self, x, y): self.append('M', x, -y)
+
def m(self, dx, dy): self.append('m', dx, -dy)
+
def L(self, x, y): self.append('L', x, -y)
+
def l(self, dx, dy): self.append('l', dx, -dy)
+
def H(self, x, y): self.append('H', x)
+
def h(self, dx): self.append('h', dx)
+
def V(self, y): self.append('V', -y)
+
def v(self, dy): self.append('v', -dy)
+
def Z(self): self.append('Z')
+
def C(self, cx1, cy1, cx2, cy2, ex, ey):
+
self.append('C', cx1, -cy1, cx2, -cy2, ex, -ey)
+
def c(self, cx1, cy1, cx2, cy2, ex, ey):
+
self.append('c', cx1, -cy1, cx2, -cy2, ex, -ey)
+
def S(self, cx2, cy2, ex, ey): self.append('S', cx2, -cy2, ex, -ey)
+
def s(self, cx2, cy2, ex, ey): self.append('s', cx2, -cy2, ex, -ey)
+
def Q(self, cx, cy, ex, ey): self.append('Q', cx, -cy, ex, -ey)
+
def q(self, cx, cy, ex, ey): self.append('q', cx, -cy, ex, -ey)
+
def T(self, ex, ey): self.append('T', ex, -ey)
+
def t(self, ex, ey): self.append('t', ex, -ey)
+
def A(self, rx, ry, rot, largeArc, sweep, ex, ey):
+
self.append('A', rx, ry, rot, int(bool(largeArc)), int(bool(sweep)), ex, -ey)
+
def a(self, rx, ry, rot, largeArc, sweep, ex, ey):
+
self.append('a', rx, ry, rot, int(bool(largeArc)), int(bool(sweep)), ex, -ey)
+
def arc(self, cx, cy, r, startDeg, endDeg, cw=False, includeM=True, includeL=False):
+
''' Uses A() to draw a circular arc '''
+
largeArc = (endDeg - startDeg) % 360 > 180
+
startRad, endRad = startDeg*math.pi/180, endDeg*math.pi/180
+
sx, sy = r*math.cos(startRad), r*math.sin(startRad)
+
ex, ey = r*math.cos(endRad), r*math.sin(endRad)
+
self.A(r, r, 0, largeArc ^ cw, cw, cx+ex, cy+ey)
+
''' A sequence of connected lines (or a polygon)
+
Additional keyword arguments are ouput as additional properties to the
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
+
def __init__(self, sx, sy, *points, close=False, **kwargs):
+
super().__init__(d='', **kwargs)
+
assert len(points) % 2 == 0
+
for i in range(len(points) // 2):
+
self.L(points[2*i], points[2*i+1])
+
Additional keyword arguments are ouput as additional properties to the
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
+
def __init__(self, sx, sy, ex, ey, **kwargs):
+
super().__init__(sx, sy, ex, ey, close=False, **kwargs)
+
Additional keyword arguments are ouput as additional properties to the
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
+
def __init__(self, cx, cy, r, startDeg, endDeg, cw=False, **kwargs):
+
super().__init__(d='', **kwargs)
+
self.arc(cx, cy, r, startDeg, endDeg, cw=cw, includeM=True)