svgwrite generates SVG files in pure Python. pip install svgwrite. Basic: import svgwrite; dwg = svgwrite.Drawing("out.svg", size=("200px","100px")). Add shape: dwg.add(dwg.rect((10,10),(180,80), fill="blue")). Save: dwg.save(). String: dwg.tostring(). Circle: dwg.circle(center=(cx,cy), r=radius, fill="red", stroke="black", stroke_width=2). Line: dwg.line(start=(x1,y1), end=(x2,y2), stroke="black"). Polyline: dwg.polyline([(0,0),(50,50),(100,0)], stroke="blue", fill="none"). Polygon: dwg.polygon([(0,0),(100,0),(50,86)], fill="green"). Text: dwg.text("Hello", insert=(x,y), fill="black", font_size="14px", font_family="Arial"). tspan: text.add(dwg.tspan("bold part", font_weight="bold")). Path: dwg.path(d="M10,10 L100,10 L100,100 Z", fill="none", stroke="black"). Group: g = dwg.g(transform="translate(10,10)"); g.add(...). Gradient: grad = dwg.linearGradient(...); grad.add_stop_color(0, "blue"); grad.add_stop_color(1, "red"); dwg.defs.add(grad). fill=grad.get_paint_server(). ViewBox: Drawing(size=("100%","100%"), viewBox="0 0 400 300"). Use: sym = dwg.symbol(); dwg.defs.add(sym); dwg.add(dwg.use(sym.get_iri(), insert=(x,y))). Opacity: fill_opacity=0.5. Clip: clip = dwg.clipPath(). Image: dwg.image(href="logo.png", insert=(0,0), size=(100,100)). Claude Code generates svgwrite bar charts, pie charts, SVG badges, and data visualization templates.
CLAUDE.md for svgwrite
## svgwrite Stack
- Version: svgwrite >= 1.4 | pip install svgwrite
- Init: dwg = svgwrite.Drawing(filename, size=(w, h), profile="full")
- Add: dwg.add(dwg.rect/circle/line/text/path(...))
- Group: g = dwg.g(transform="..."); g.add(shape); dwg.add(g)
- Gradient: defs.add(grad); use fill=grad.get_paint_server()
- Output: dwg.tostring() → str | dwg.save() → file
svgwrite SVG Generation Pipeline
# app/svg_gen.py — svgwrite bar chart, line chart, badges, and shape helpers
from __future__ import annotations
import math
from typing import Any
import svgwrite
from svgwrite import Drawing
from svgwrite.container import Group
# ─────────────────────────────────────────────────────────────────────────────
# 1. Drawing factory
# ─────────────────────────────────────────────────────────────────────────────
def make_drawing(
width: int,
height: int,
filename: str = "out.svg",
responsive: bool = False,
) -> Drawing:
"""
Create an svgwrite Drawing.
responsive=True: sets size to 100%/100% with a viewBox so the SVG scales.
"""
if responsive:
return svgwrite.Drawing(
filename,
size=("100%", "100%"),
viewBox=f"0 0 {width} {height}",
profile="full",
)
return svgwrite.Drawing(filename, size=(width, height), profile="full")
def svg_string(dwg: Drawing) -> str:
"""Return the SVG as a UTF-8 string."""
return dwg.tostring()
# ─────────────────────────────────────────────────────────────────────────────
# 2. Bar chart
# ─────────────────────────────────────────────────────────────────────────────
def bar_chart(
data: list[tuple[str, float]],
title: str = "",
width: int = 480,
height: int = 300,
bar_color: str = "#4a90d9",
bg_color: str = "#ffffff",
font_family: str = "Arial, sans-serif",
) -> str:
"""
Generate a vertical bar chart SVG.
data: [(label, value), ...]
Returns SVG string.
"""
pad_left, pad_right, pad_top, pad_bottom = 50, 20, 40, 60
chart_w = width - pad_left - pad_right
chart_h = height - pad_top - pad_bottom
dwg = make_drawing(width, height)
# Background
dwg.add(dwg.rect((0, 0), (width, height), fill=bg_color))
# Title
if title:
dwg.add(dwg.text(
title,
insert=(width / 2, 22),
text_anchor="middle",
fill="#333",
font_size="13px",
font_family=font_family,
font_weight="bold",
))
if not data:
return svg_string(dwg)
max_val = max(v for _, v in data) or 1
n = len(data)
bar_w = chart_w / n * 0.6
gap = chart_w / n
for i, (label, value) in enumerate(data):
x = pad_left + i * gap + (gap - bar_w) / 2
bar_h = (value / max_val) * chart_h
y = pad_top + chart_h - bar_h
# Bar
dwg.add(dwg.rect(
(x, y), (bar_w, bar_h),
fill=bar_color, rx=3, ry=3,
))
# Value label above bar
dwg.add(dwg.text(
f"{value:,.0f}",
insert=(x + bar_w / 2, y - 4),
text_anchor="middle",
fill="#333",
font_size="9px",
font_family=font_family,
))
# X axis label
dwg.add(dwg.text(
label,
insert=(x + bar_w / 2, pad_top + chart_h + 16),
text_anchor="middle",
fill="#555",
font_size="10px",
font_family=font_family,
))
# Axis line
dwg.add(dwg.line(
start=(pad_left, pad_top + chart_h),
end=(width - pad_right, pad_top + chart_h),
stroke="#ccc",
stroke_width=1,
))
return svg_string(dwg)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Line chart
# ─────────────────────────────────────────────────────────────────────────────
def line_chart(
series: dict[str, list[tuple[str, float]]],
title: str = "",
width: int = 480,
height: int = 280,
colors: list[str] | None = None,
font_family: str = "Arial, sans-serif",
) -> str:
"""
Multi-series line chart.
series: {series_name: [(label, value), ...]}
"""
COLORS = colors or ["#4a90d9", "#e74c3c", "#2ecc71", "#f39c12", "#9b59b6"]
pad_left, pad_right, pad_top, pad_bottom = 50, 20, 40, 60
chart_w = width - pad_left - pad_right
chart_h = height - pad_top - pad_bottom
dwg = make_drawing(width, height)
dwg.add(dwg.rect((0, 0), (width, height), fill="#fff"))
if title:
dwg.add(dwg.text(title, insert=(width / 2, 22), text_anchor="middle",
fill="#333", font_size="13px", font_family=font_family,
font_weight="bold"))
all_vals = [v for pts in series.values() for _, v in pts]
if not all_vals:
return svg_string(dwg)
max_val = max(all_vals) or 1
all_labels = [lbl for _, pts in series.items() for lbl, _ in pts[:1]]
def to_xy(idx: int, val: float, n: int) -> tuple[float, float]:
x = pad_left + (idx / max(n - 1, 1)) * chart_w
y = pad_top + chart_h - (val / max_val) * chart_h
return x, y
for s_idx, (name, points) in enumerate(series.items()):
color = COLORS[s_idx % len(COLORS)]
n = len(points)
coords = [to_xy(i, v, n) for i, (_, v) in enumerate(points)]
polyline_pts = [(round(x, 1), round(y, 1)) for x, y in coords]
dwg.add(dwg.polyline(polyline_pts, stroke=color, stroke_width=2,
fill="none", stroke_linejoin="round",
stroke_linecap="round"))
# Data points
for x, y in coords:
dwg.add(dwg.circle(center=(x, y), r=3, fill=color, stroke="#fff",
stroke_width=1.5))
# Legend entry
leg_x = pad_left + s_idx * 100
if leg_x + 90 < width:
dwg.add(dwg.line(start=(leg_x, height - 18), end=(leg_x + 16, height - 18),
stroke=color, stroke_width=2))
dwg.add(dwg.text(name, insert=(leg_x + 20, height - 14), fill="#555",
font_size="9px", font_family=font_family))
# X labels
first_series = next(iter(series.values()), [])
n = len(first_series)
for i, (label, _) in enumerate(first_series):
x, _ = to_xy(i, 0, n)
dwg.add(dwg.text(label, insert=(x, pad_top + chart_h + 14),
text_anchor="middle", fill="#666", font_size="9px",
font_family=font_family))
# Axes
dwg.add(dwg.line(start=(pad_left, pad_top), end=(pad_left, pad_top + chart_h),
stroke="#ddd", stroke_width=1))
dwg.add(dwg.line(start=(pad_left, pad_top + chart_h),
end=(width - pad_right, pad_top + chart_h),
stroke="#ddd", stroke_width=1))
return svg_string(dwg)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Badge generator (shields.io style)
# ─────────────────────────────────────────────────────────────────────────────
def badge(
label: str,
value: str,
label_color: str = "#555",
value_color: str = "#4c1",
height: int = 20,
font_size: int = 11,
) -> str:
"""
Generate a flat badge SVG (shields.io style).
Returns SVG string.
"""
char_w = font_size * 0.62
label_w = int(len(label) * char_w + 14)
value_w = int(len(value) * char_w + 14)
total_w = label_w + value_w
dwg = make_drawing(total_w, height)
font_family = "DejaVu Sans,Verdana,Geneva,sans-serif"
fy = height / 2 + font_size * 0.36 # vertical center
# Background rects
dwg.add(dwg.rect((0, 0), (label_w, height), fill=label_color, rx=3, ry=3))
dwg.add(dwg.rect((label_w - 3, 0), (value_w + 3, height), fill=value_color))
# Right cap
dwg.add(dwg.rect((total_w - 3, 0), (3, height), fill=value_color, rx=3, ry=3))
# Text shadow + text
for txt, x_center, fill, shadow in [
(label, label_w / 2, "#fff", True),
(value, label_w + value_w / 2, "#fff", True),
]:
if shadow:
dwg.add(dwg.text(txt, insert=(x_center, fy + 1),
text_anchor="middle", fill="#0003",
font_size=f"{font_size}px", font_family=font_family))
dwg.add(dwg.text(txt, insert=(x_center, fy),
text_anchor="middle", fill=fill,
font_size=f"{font_size}px", font_family=font_family))
return svg_string(dwg)
# ─────────────────────────────────────────────────────────────────────────────
# 5. Pie / donut chart
# ─────────────────────────────────────────────────────────────────────────────
def pie_chart(
data: list[tuple[str, float]],
title: str = "",
size: int = 260,
donut: bool = False,
colors: list[str] | None = None,
font_family: str = "Arial, sans-serif",
) -> str:
"""
Generate a pie or donut chart SVG.
data: [(label, value), ...] — values are proportional (need not sum to 1).
"""
COLORS = colors or ["#4a90d9","#e74c3c","#2ecc71","#f39c12","#9b59b6",
"#1abc9c","#e67e22","#3498db","#e91e63","#607d8b"]
cx = cy = size / 2
r = size / 2 * 0.75
ri = r * 0.5 # inner radius for donut
total = sum(v for _, v in data) or 1
dwg = make_drawing(size, size)
dwg.add(dwg.rect((0, 0), (size, size), fill="#fff"))
if title:
dwg.add(dwg.text(title, insert=(cx, 14), text_anchor="middle",
fill="#333", font_size="12px", font_family=font_family,
font_weight="bold"))
def polar(angle: float, radius: float) -> tuple[float, float]:
return (cx + radius * math.cos(angle), cy + radius * math.sin(angle))
start = -math.pi / 2
for i, (label, value) in enumerate(data):
sweep = (value / total) * 2 * math.pi
end = start + sweep
color = COLORS[i % len(COLORS)]
# Arc path
x1, y1 = polar(start, r)
x2, y2 = polar(end, r)
large = 1 if sweep > math.pi else 0
if donut:
xi1, yi1 = polar(start, ri)
xi2, yi2 = polar(end, ri)
d = (f"M {x1:.2f} {y1:.2f} "
f"A {r:.2f} {r:.2f} 0 {large} 1 {x2:.2f} {y2:.2f} "
f"L {xi2:.2f} {yi2:.2f} "
f"A {ri:.2f} {ri:.2f} 0 {large} 0 {xi1:.2f} {yi1:.2f} Z")
else:
d = (f"M {cx:.2f} {cy:.2f} "
f"L {x1:.2f} {y1:.2f} "
f"A {r:.2f} {r:.2f} 0 {large} 1 {x2:.2f} {y2:.2f} Z")
dwg.add(dwg.path(d=d, fill=color, stroke="#fff", stroke_width=1.5))
# Label at midpoint
mid = start + sweep / 2
lx, ly = polar(mid, r * (0.75 if donut else 0.6))
pct = value / total * 100
if pct > 5:
dwg.add(dwg.text(f"{pct:.0f}%", insert=(lx, ly), text_anchor="middle",
fill="#fff", font_size="9px", font_family=font_family,
font_weight="bold"))
start = end
return svg_string(dwg)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
from pathlib import Path
print("=== Bar chart ===")
svg = bar_chart(
[("Jan", 120), ("Feb", 85), ("Mar", 210), ("Apr", 175), ("May", 95)],
title="Monthly Revenue ($K)",
)
Path("/tmp/bar.svg").write_text(svg)
print(f" bar.svg: {len(svg)} chars")
print("=== Line chart ===")
svg = line_chart({
"Revenue": [("Q1", 120), ("Q2", 145), ("Q3", 98), ("Q4", 210)],
"Costs": [("Q1", 80), ("Q2", 95), ("Q3", 75), ("Q4", 130)],
}, title="Revenue vs Costs")
Path("/tmp/line.svg").write_text(svg)
print(f" line.svg: {len(svg)} chars")
print("=== Badges ===")
Path("/tmp/badge_build.svg").write_text(badge("build", "passing"))
Path("/tmp/badge_cov.svg").write_text(badge("coverage", "94%", value_color="#007ec6"))
Path("/tmp/badge_ver.svg").write_text(badge("version", "1.2.0", value_color="#e05d44"))
print(" badges written")
print("=== Pie / donut ===")
sales_data = [("Python", 42), ("JavaScript", 28), ("Go", 15), ("Rust", 10), ("Other", 5)]
Path("/tmp/pie.svg").write_text(pie_chart(sales_data, title="Language Usage"))
Path("/tmp/donut.svg").write_text(pie_chart(sales_data, title="Language Usage", donut=True))
print(" pie.svg and donut.svg written")
For the matplotlib SVG backend alternative — matplotlib’s savefig("out.svg") produces SVG but includes heavy NumPy/matplotlib dependencies and generates verbose output optimized for viewing, not embedding; svgwrite produces minimal, hand-readable SVG with precise coordinate control, ideal for badges, UI icons, and embed-in-HTML charts that don’t require a full scientific plotting backend. For the cairosvg alternative — cairosvg converts SVG→PNG/PDF using the Cairo graphics library (requires libcairo OS dependency); svgwrite is the generator side — you write SVG with svgwrite and optionally convert to raster/PDF with cairosvg in a two-step pipeline, which keeps SVG generation dependency-free. The Claude Skills 360 bundle includes svgwrite skill sets covering make_drawing()/svg_string(), bar_chart() with value labels and axis, line_chart() multi-series with legend, badge() shield-style flat badge, pie_chart()/donut chart with percentage labels, dwg.rect/circle/line/polyline/polygon primitives, dwg.text/tspan typography, dwg.path() with d-string, dwg.g() groups and transforms, and linearGradient with stop colors. Start with the free tier to try SVG vector graphics code generation.