The class presented below may not be the best in town, yet the focus is on demonstrating the use of print statements — an essential tool available in nearly every programming language. Specifically, in Python, performance can be further enhanced by utilising sys.stdout.
SVG code from common applications with SVG export often results in unreadable files, e.g., with a lot of unnecessary clipping that cannot even be disentangled with Scour.
class SVG:
def __init__(self, width, height):
self.width = width
self.height = height
self.elements = []
self.defs = []
def rect(self, x, y, width, height, fill="none", stroke="none"):
self.elements.append(
f'<rect x="{x}" y="{y}" width="{width}" height="{height}" fill="{fill}" stroke="{stroke}" />'
)
def circle(self, cx, cy, r, fill="none", stroke="none"):
self.elements.append(
f'<circle cx="{cx}" cy="{cy}" r="{r}" fill="{fill}" stroke="{stroke}" />'
)
def line(self, x1, y1, x2, y2, stroke="black"):
self.elements.append(
f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="{stroke}" />'
)
def text(self, x, y, content, font_size=12, fill="black"):
self.elements.append(
f'<text x="{x}" y="{y}" font-size="{font_size}" fill="{fill}">{content}</text>'
)
def path(self, d, fill="none", stroke="black", stroke_width=1):
self.elements.append(
f'<path d="{d}" fill="{fill}" stroke="{stroke}" stroke-width="{stroke_width}" />'
)
def define_symbol(self, symbol_id, body, viewbox="0 0 100 100"):
"""Add a <symbol> to <defs> section. `body` is a list of raw SVG strings."""
symbol = (
f'<symbol id="{symbol_id}" viewBox="{viewbox}">\n'
+ "\n".join(body)
+ "\n</symbol>"
)
self.defs.append(symbol)
def use(self, symbol_id, transform=None):
attrs = [f'xlink:href="#{symbol_id}"']
if transform:
attrs.append(f'transform="{transform}"')
self.elements.append(f'<use {" ".join(attrs)} />')
def to_string(self):
defs_block = ""
if self.defs:
defs_block = "<defs>\n" + "\n".join(self.defs) + "\n</defs>\n"
viewBox = f"{-self.width / 2} {-self.height / 2} {self.width} {self.height}"
return (
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{viewBox}">\n'
+ defs_block
+ "\n".join(self.elements)
+ "\n</svg>"
)
1. Create an instance of SVG with a width and height leading to
viewBox="-width/2 -height/2 width height"
2. Add shapes using the available methods: rect, circle, line,
text, path.
3. Use define_symbol and use to create reusable SVG elements.
4. Generate the final SVG output with to_string()
# Example Usage (viewBox="-250 -250 500 500")
svg = SVG(width=500, height=500)
# Add concentric circles
center_x = -100
center_y = -20
max_radius = 125
num_circles = 25
for i in range(1, num_circles + 1):
r = (max_radius / num_circles) * i
svg.circle(cx=center_x, cy=center_y, r=r, stroke="black", fill="none")
# Add a complex path directly
svg.path("M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80", stroke="blue", fill="none")
svg.define_symbol(symbol_id="sqrt", body=[f'<path d="M 2,12 H 5 L 7,19 11,4 22,4" stroke="black" fill="none" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" />'])
# Use the defined square root symbol
svg.use(symbol_id="sqrt", transform="matrix(1, 0, 0, 1, -240, -240)")
# Print the result
print(svg.to_string())
# Type in the terminal
# python svgclass.py > output.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-250.0 -250.0 500 500">
<defs>
<symbol id="sqrt" viewBox="0 0 100 100">
<path d="M 2,12 H 5 L 7,19 11,4 22,4" stroke="black" fill="none" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" />
</symbol>
</defs>
<circle cx="-100" cy="-20" r="5.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="10.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="15.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="20.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="25.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="30.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="35.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="40.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="45.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="50.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="55.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="60.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="65.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="70.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="75.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="80.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="85.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="90.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="95.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="100.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="105.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="110.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="115.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="120.0" fill="none" stroke="black" />
<circle cx="-100" cy="-20" r="125.0" fill="none" stroke="black" />
<path d="M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80" fill="none" stroke="blue" stroke-width="1" />
<use xlink:href="#sqrt" transform="matrix(1, 0, 0, 1, -240, -240)" />
</svg>
As you can see, this code is simple; it can easily be reduced by grouping e.g. the circles with <g fill="none" stroke="black">...</g>. Most optimisation tools will do this.