Generating and Manipulating SVGs with OpenClaw Agents
SVG (Scalable Vector Graphics) is perfect for AI agents. It's text-based, programmable, and renders anywhere. Agents can generate charts, diagrams, icons, and visualizations without external APIs or complex libraries.
This guide shows you how OpenClaw agents can create and manipulate SVGs for data visualization, dynamic images, and more.
Why SVG for Agents?
Text-Based
SVG is XML. Agents can generate it with string manipulation:
<svg width="200" height="200">
<circle cx="100" cy="100" r="50" fill="blue" />
</svg>
No image processing libraries needed.
Scalable
Vector graphics scale without pixelation. Perfect for responsive designs and high-DPI displays.
Interactive
SVGs support CSS, JavaScript, and interactivity. Agents can create dynamic, animated visualizations.
Lightweight
SVGs are typically smaller than raster images. Fast to generate, fast to transmit.
No External Dependencies
Unlike PNG/JPEG generation (which needs ImageMagick, Pillow, etc.), SVG is pure text. Agents can generate complex graphics with zero dependencies.
Basic SVG Generation
Simple Shapes
function generateCircle(x: number, y: number, radius: number, color: string) {
return `
<svg width="${radius * 2}" height="${radius * 2}" xmlns="http://www.w3.org/2000/svg">
<circle cx="${x}" cy="${y}" r="${radius}" fill="${color}" />
</svg>
`.trim();
}
const svg = generateCircle(100, 100, 50, "steelblue");
await fs.writeFile("circle.svg", svg);
Multiple Shapes
function generateShapes() {
return `
<svg width="300" height="300" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="100" height="100" fill="red" />
<circle cx="200" cy="60" r="50" fill="blue" />
<polygon points="150,200 200,250 100,250" fill="green" />
</svg>
`.trim();
}
Data Visualization
Bar Chart
interface DataPoint {
label: string;
value: number;
}
function generateBarChart(data: DataPoint[], options = {}) {
const width = options.width || 500;
const height = options.height || 300;
const padding = options.padding || 40;
const maxValue = Math.max(...data.map(d => d.value));
const barWidth = (width - padding * 2) / data.length;
const chartHeight = height - padding * 2;
const bars = data.map((d, i) => {
const barHeight = (d.value / maxValue) * chartHeight;
const x = padding + i * barWidth;
const y = height - padding - barHeight;
return `
<g>
<rect
x="${x}"
y="${y}"
width="${barWidth * 0.8}"
height="${barHeight}"
fill="steelblue"
/>
<text
x="${x + barWidth / 2}"
y="${height - padding + 20}"
text-anchor="middle"
font-size="12"
>${d.label}</text>
<text
x="${x + barWidth / 2}"
y="${y - 5}"
text-anchor="middle"
font-size="10"
>${d.value}</text>
</g>
`;
}).join("");
return `
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
${bars}
</svg>
`.trim();
}
const data = [
{ label: "Jan", value: 45 },
{ label: "Feb", value: 62 },
{ label: "Mar", value: 58 },
{ label: "Apr", value: 71 }
];
const chart = generateBarChart(data);
await fs.writeFile("bar-chart.svg", chart);
Line Chart
function generateLineChart(data: number[], options = {}) {
const width = options.width || 500;
const height = options.height || 300;
const padding = options.padding || 40;
const maxValue = Math.max(...data);
const minValue = Math.min(...data);
const range = maxValue - minValue;
const chartWidth = width - padding * 2;
const chartHeight = height - padding * 2;
const stepX = chartWidth / (data.length - 1);
const points = data.map((value, i) => {
const x = padding + i * stepX;
const y = height - padding - ((value - minValue) / range) * chartHeight;
return `${x},${y}`;
}).join(" ");
return `
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<polyline
points="${points}"
fill="none"
stroke="steelblue"
stroke-width="2"
/>
</svg>
`.trim();
}
const values = [10, 25, 18, 42, 35, 50, 45];
const lineChart = generateLineChart(values);
await fs.writeFile("line-chart.svg", lineChart);
Pie Chart
function generatePieChart(data: DataPoint[], options = {}) {
const size = options.size || 300;
const radius = size / 2 - 10;
const cx = size / 2;
const cy = size / 2;
const total = data.reduce((sum, d) => sum + d.value, 0);
let currentAngle = 0;
const slices = data.map((d, i) => {
const sliceAngle = (d.value / total) * 360;
const startAngle = currentAngle;
const endAngle = currentAngle + sliceAngle;
currentAngle = endAngle;
const x1 = cx + radius * Math.cos((startAngle - 90) * Math.PI / 180);
const y1 = cy + radius * Math.sin((startAngle - 90) * Math.PI / 180);
const x2 = cx + radius * Math.cos((endAngle - 90) * Math.PI / 180);
const y2 = cy + radius * Math.sin((endAngle - 90) * Math.PI / 180);
const largeArc = sliceAngle > 180 ? 1 : 0;
const color = `hsl(${i * 360 / data.length}, 70%, 60%)`;
return `
<path
d="M ${cx} ${cy} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z"
fill="${color}"
stroke="white"
stroke-width="2"
/>
`;
}).join("");
return `
<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
${slices}
</svg>
`.trim();
}
Real-World Use Cases
Use Case 1: Agent Activity Dashboard
Visualize agent metrics:
async function generateActivityDashboard(metrics: AgentMetrics) {
const tasks = generateBarChart([
{ label: "Completed", value: metrics.tasksCompleted },
{ label: "In Progress", value: metrics.tasksInProgress },
{ label: "Pending", value: metrics.tasksPending }
]);
const activity = generateLineChart(metrics.dailyActivity);
const dashboard = `
<svg width="1000" height="600" xmlns="http://www.w3.org/2000/svg">
<text x="500" y="30" text-anchor="middle" font-size="24" font-weight="bold">
Agent Activity Dashboard
</text>
<g transform="translate(0, 50)">
${tasks}
</g>
<g transform="translate(500, 50)">
${activity}
</g>
</svg>
`.trim();
await fs.writeFile("dashboard.svg", dashboard);
}
Use Case 2: System Architecture Diagram
Generate diagrams programmatically:
function generateArchitectureDiagram(components: Component[]) {
const boxes = components.map((c, i) => {
const x = 50 + (i % 3) * 300;
const y = 50 + Math.floor(i / 3) * 200;
return `
<g>
<rect
x="${x}" y="${y}"
width="250" height="150"
fill="white"
stroke="black"
stroke-width="2"
rx="10"
/>
<text
x="${x + 125}"
y="${y + 75}"
text-anchor="middle"
font-size="16"
font-weight="bold"
>${c.name}</text>
<text
x="${x + 125}"
y="${y + 100}"
text-anchor="middle"
font-size="12"
fill="gray"
>${c.description}</text>
</g>
`;
}).join("");
return `
<svg width="1000" height="800" xmlns="http://www.w3.org/2000/svg">
${boxes}
</svg>
`.trim();
}
Use Case 3: Progress Indicators
Dynamic progress visualization:
function generateProgressCircle(percentage: number, options = {}) {
const size = options.size || 200;
const radius = size / 2 - 10;
const cx = size / 2;
const cy = size / 2;
const strokeWidth = options.strokeWidth || 20;
const circumference = 2 * Math.PI * radius;
const progress = (percentage / 100) * circumference;
return `
<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
<circle
cx="${cx}" cy="${cy}" r="${radius}"
fill="none"
stroke="#e0e0e0"
stroke-width="${strokeWidth}"
/>
<circle
cx="${cx}" cy="${cy}" r="${radius}"
fill="none"
stroke="steelblue"
stroke-width="${strokeWidth}"
stroke-dasharray="${circumference}"
stroke-dashoffset="${circumference - progress}"
transform="rotate(-90 ${cx} ${cy})"
/>
<text
x="${cx}" y="${cy}"
text-anchor="middle"
dominant-baseline="middle"
font-size="48"
font-weight="bold"
>${percentage}%</text>
</svg>
`.trim();
}
Advanced Techniques
Gradients
function createGradient() {
return `
<svg width="300" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
</linearGradient>
</defs>
<rect width="300" height="200" fill="url(#grad1)" />
</svg>
`.trim();
}
Animations
function createAnimation() {
return `
<svg width="300" height="300" xmlns="http://www.w3.org/2000/svg">
<circle cx="150" cy="150" r="50" fill="steelblue">
<animate
attributeName="r"
from="50"
to="100"
dur="2s"
repeatCount="indefinite"
/>
</circle>
</svg>
`.trim();
}
Patterns
function createPattern() {
return `
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="gray" stroke-width="0.5"/>
</pattern>
</defs>
<rect width="400" height="400" fill="url(#grid)" />
</svg>
`.trim();
}
OpenClaw Canvas Integration
OpenClaw's canvas feature renders SVGs visually:
import { canvas } from "openclaw";
const svg = generateBarChart(data);
// Display in OpenClaw canvas
await canvas.present({
action: "present",
url: `data:image/svg+xml,${encodeURIComponent(svg)}`
});
Exporting to Other Formats
Convert to PNG (requires external tool)
# Using Inkscape
inkscape chart.svg --export-filename=chart.png
# Using ImageMagick
convert chart.svg chart.png
# Using rsvg-convert
rsvg-convert -o chart.png chart.svg
Embed in HTML
<!DOCTYPE html>
<html>
<body>
<img src="chart.svg" alt="Chart" />
<!-- Or inline -->
<svg width="200" height="200">
<circle cx="100" cy="100" r="50" fill="blue" />
</svg>
</body>
</html>
Best Practices
1. Use ViewBox for Scalability
<svg viewBox="0 0 500 300" xmlns="http://www.w3.org/2000/svg">
<!-- Content scales automatically -->
</svg>
2. Optimize for File Size
Remove unnecessary whitespace:
const svg = generateChart(data)
.replace(/\s+/g, " ")
.trim();
3. Use Semantic Grouping
<svg>
<g id="data-points">
<!-- All data points here -->
</g>
<g id="axes">
<!-- Axes here -->
</g>
</svg>
4. Add Accessibility
<svg role="img" aria-label="Bar chart showing monthly sales">
<title>Monthly Sales Chart</title>
<desc>Sales data from January to December</desc>
<!-- Content -->
</svg>
5. Validate SVG
Use online validators or tools:
xmllint --noout --schema svg.xsd your-chart.svg
Wrapping Up
SVG generation gives OpenClaw agents powerful visualization capabilities with zero external dependencies. From simple shapes to complex dashboards, agents can create graphics programmatically.
Start with basic shapes. Build up to charts. Then create domain-specific visualizations for your use case.
Images speak louder than logs. Give your agents the power to visualize their work.