···
4
+
# TODO: Support drawing ellipses without manually using Path
7
+
class DrawingElement:
8
+
''' Base class for drawing elements
10
+
Subclasses must implement writeSvgElement '''
11
+
def writeSvgElement(self, outputFile):
12
+
raise NotImplementedError('Abstract base class')
13
+
def __eq__(self, other):
14
+
return self is other
16
+
class DrawingBasicElement(DrawingElement):
17
+
''' Base class for SVG drawing elements that are a single node with no
20
+
def __init__(self, **args):
22
+
def writeSvgElement(self, outputFile):
23
+
outputFile.write('<')
24
+
outputFile.write(self.TAG_NAME)
25
+
outputFile.write(' ')
26
+
for k, v in self.args.items():
27
+
k = k.replace('_', '-')
28
+
outputFile.write('{}="{}" '.format(k,v))
29
+
outputFile.write('/>')
30
+
def __eq__(self, other):
31
+
if isinstance(other, type(self)):
32
+
return (self.tagName == other.tagName and
33
+
self.args == other.args)
36
+
class NoElement(DrawingElement):
37
+
''' A drawing element that has no effect '''
38
+
def __init__(self): pass
39
+
def writeSvgElement(self, outputFile):
41
+
def __eq__(self, other):
42
+
if isinstance(other, type(self)):
46
+
class Rectangle(DrawingBasicElement):
49
+
Additional keyword arguments are ouput as additional arguments to the
50
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
52
+
def __init__(self, x, y, width, height, **kwargs):
53
+
super().__init__(x=x, y=-y-height, width=width, height=height,
56
+
class Circle(DrawingBasicElement):
59
+
Additional keyword arguments are ouput as additional properties to the
60
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
62
+
def __init__(self, cx, cy, r, **kwargs):
63
+
super().__init__(cx=cx, cy=-cy, r=r, **kwargs)
65
+
class ArcLine(Circle):
68
+
In most cases, use Arc instead of ArcLine. ArcLine uses the
69
+
stroke-dasharray SVG property to make the edge of a circle look like
72
+
Additional keyword arguments are ouput as additional arguments to the
73
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
74
+
def __init__(self, cx, cy, r, startDeg, endDeg, **kwargs):
75
+
if endDeg - startDeg == 360:
76
+
super().__init__(cx, cy, r, **kwargs)
78
+
startDeg, endDeg = (-endDeg) % 360, (-startDeg) % 360
79
+
arcDeg = (endDeg - startDeg) % 360
80
+
def arcLen(deg): return math.radians(deg) * r
81
+
wholeLen = 2 * math.pi * r
82
+
if endDeg == startDeg:
84
+
dashes = "0 {}".format(wholeLen+2)
85
+
#elif endDeg >= startDeg:
87
+
startLen = arcLen(startDeg)
88
+
arcLen = arcLen(arcDeg)
89
+
offLen = wholeLen - arcLen
91
+
dashes = "{} {}".format(arcLen, offLen)
93
+
# firstLen = arcLen(endDeg)
94
+
# secondLen = arcLen(360-startDeg)
95
+
# gapLen = wholeLen - firstLen - secondLen
97
+
# dashes = "{} {} {}".format(firstLen, gapLen, secondLen)
98
+
super().__init__(cx, cy, r, stroke_dasharray=dashes,
99
+
stroke_dashoffset=offset, **kwargs)
101
+
class Path(DrawingBasicElement):
102
+
''' An arbitrary path
104
+
Path Supports building an SVG path by calling instance methods
105
+
corresponding to path commands.
107
+
Additional keyword arguments are ouput as additional properties to the
108
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
110
+
def __init__(self, d='', **kwargs):
111
+
super().__init__(d=d, **kwargs)
112
+
def append(self, commandStr, *args):
113
+
if len(self.args['d']) > 0:
114
+
commandStr = ' ' + commandStr
116
+
commandStr = commandStr + ','.join(map(str, args))
117
+
self.args['d'] += commandStr
118
+
def M(self, x, y): self.append('M', x, -y)
119
+
def m(self, dx, dy): self.append('m', dx, -dy)
120
+
def L(self, x, y): self.append('L', x, -y)
121
+
def l(self, dx, dy): self.append('l', dx, -dy)
122
+
def H(self, x, y): self.append('H', x)
123
+
def h(self, dx): self.append('h', dx)
124
+
def V(self, y): self.append('V', -y)
125
+
def v(self, dy): self.append('v', -dy)
126
+
def Z(self): self.append('Z')
127
+
def C(self, cx1, cy1, cx2, cy2, ex, ey):
128
+
self.append('C', cx1, -cy1, cx2, -cy2, ex, -ey)
129
+
def c(self, cx1, cy1, cx2, cy2, ex, ey):
130
+
self.append('c', cx1, -cy1, cx2, -cy2, ex, -ey)
131
+
def S(self, cx2, cy2, ex, ey): self.append('S', cx2, -cy2, ex, -ey)
132
+
def s(self, cx2, cy2, ex, ey): self.append('s', cx2, -cy2, ex, -ey)
133
+
def Q(self, cx, cy, ex, ey): self.append('Q', cx, -cy, ex, -ey)
134
+
def q(self, cx, cy, ex, ey): self.append('q', cx, -cy, ex, -ey)
135
+
def T(self, ex, ey): self.append('T', ex, -ey)
136
+
def t(self, ex, ey): self.append('t', ex, -ey)
137
+
def A(self, rx, ry, rot, largeArc, sweep, ex, ey):
138
+
self.append('A', rx, ry, rot, int(bool(largeArc)), int(bool(sweep)), ex, -ey)
139
+
def a(self, rx, ry, rot, largeArc, sweep, ex, ey):
140
+
self.append('a', rx, ry, rot, int(bool(largeArc)), int(bool(sweep)), ex, -ey)
141
+
def arc(self, cx, cy, r, startDeg, endDeg, cw=False, includeM=True, includeL=False):
142
+
''' Uses A() to draw a circular arc '''
143
+
largeArc = (endDeg - startDeg) % 360 > 180
144
+
startRad, endRad = startDeg*math.pi/180, endDeg*math.pi/180
145
+
sx, sy = r*math.cos(startRad), r*math.sin(startRad)
146
+
ex, ey = r*math.cos(endRad), r*math.sin(endRad)
148
+
self.L(cx+sx, cy+sy)
150
+
self.M(cx+sx, cy+sy)
151
+
self.A(r, r, 0, largeArc ^ cw, cw, cx+ex, cy+ey)
154
+
''' A sequence of connected lines (or a polygon)
156
+
Additional keyword arguments are ouput as additional properties to the
157
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
158
+
def __init__(self, sx, sy, *points, close=False, **kwargs):
159
+
super().__init__(d='', **kwargs)
161
+
assert len(points) % 2 == 0
162
+
for i in range(len(points) // 2):
163
+
self.L(points[2*i], points[2*i+1])
170
+
Additional keyword arguments are ouput as additional properties to the
171
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
172
+
def __init__(self, sx, sy, ex, ey, **kwargs):
173
+
super().__init__(sx, sy, ex, ey, close=False, **kwargs)
178
+
Additional keyword arguments are ouput as additional properties to the
179
+
SVG node e.g. fill="red", stroke="#ff4477", stroke_width=2. '''
180
+
def __init__(self, cx, cy, r, startDeg, endDeg, cw=False, **kwargs):
181
+
super().__init__(d='', **kwargs)
182
+
self.arc(cx, cy, r, startDeg, endDeg, cw=cw, includeM=True)