Python SVG Class Documentation

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 Source Code


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>"
        )

How to Use

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()

The example below will produce an SVG with 25 concentric circles centered at (-100, -20) and a Bézier curve path in blue, as well as a reusable square root symbol.


# 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

Example Output

SVG Code


<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.


Some Web Resources for SVG Optimisation


SQRT.CH | IMPRESSUM | HTML 5 | CSS