SVG scenes
Use
template svg:to wire Interact controls / Computed / Timer into a declarative SVG scene — draw springs, pendulums, planets, circuits, any 2D physics / geometry animation. Freer than Vega-Lite chart (any shape) but requires SVG knowledge.
When to use
- ✅ Physics simulations (springs / pendulums / planetary orbits / damped oscillators)
- ✅ Geometry demonstrations (trigonometry / circular motion / reflection-refraction / geometric transforms)
- ✅ Circuit / fluid / optical-path diagrams (with dynamic parameters)
- ✅ Custom icons / data visualization (shapes vega can't express)
- ✅ Simple character animations (robot waving, expression changes, toy motion)
- ❌ Data charts (line / bar / scatter) → use Vega-Lite, much simpler
- ❌ Complex 3D (SVG is 2D; for 3D use WebGL / Three.js — Rho doesn't directly support)
- ❌ Pixel art / bitmap (use
<img>, not SVG)
Basic syntax
```interact
slider angle range=0..360 step=1 default=0 label="Angle"
template svg:
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke="#888" stroke-width="0.5"/>
<line x1="50" y1="50"
x2="{round(50 + 40*cos(angle*pi/180), 2)}"
y2="{round(50 + 40*sin(angle*pi/180), 2)}"
stroke="red" stroke-width="2"/>
</svg>
```
After template svg: is SVG markup — {var} placeholders work in any attribute.
Renders: a circle + a red line from center, drag the angle slider to rotate the line.
Placeholders in SVG attributes
{expression} can appear in any SVG attribute value, evaluated and substituted:
<circle cx="{round(50 + 40*cos(angle*pi/180), 2)}"
cy="{round(50 + 40*sin(angle*pi/180), 2)}"
r="{radius}"
fill="{isActive == 'true' ? 'red' : 'blue'}"
opacity="{round(alpha, 2)}"/>
The mini DSL operations (see Computed) — trigonometric / power / ternary — all usable inside {}. There is no Python-style format-spec suffix after the expression; use round(expr, N) for a fixed number of decimals, and single-quoted string literals inside placeholders since the placeholder already sits inside a double-quoted attribute.
Safety (SVG sanitizer)
Rho includes a built-in SVG sanitizer that strips dangerous elements / attributes:
| Stripped | Why |
|---|---|
<script> |
Arbitrary JS execution |
<foreignObject> |
Embeds HTML (potential XSS) |
on* event attrs (onclick / onload / etc.) |
Inline JS |
href containing javascript: URLs |
XSS |
<image href="external://..."> |
Prevent external resource tracking |
Preserved:
- Standard SVG shapes (rect, circle, line, path, polygon, ellipse, polyline)
- Text (text, tspan)
- Containers (g, defs, symbol, use)
- Gradients / patterns (linearGradient, radialGradient, pattern)
- Standard attributes (cx, cy, x, y, width, height, fill, stroke, transform, etc.)
<title>/<desc>(accessibility-friendly)<animate>/<animateTransform>(standard SVG animation)
The safety design means SVG is fully declarative + cannot execute JS — extending Rho's overall safety stance.
Examples
Example 1: circular motion (manual sliders)
```interact
slider angle range=0..360 step=1 default=0 label="Angle"
slider radius range=10..50 step=1 default=40 label="Radius"
template svg:
<svg viewBox="0 0 100 100" width="200" height="200">
<circle cx="50" cy="50" r="{radius}" fill="none" stroke="#888" stroke-width="0.5" stroke-dasharray="2,2"/>
<line x1="50" y1="50"
x2="{round(50 + radius*cos(angle*pi/180), 2)}"
y2="{round(50 + radius*sin(angle*pi/180), 2)}"
stroke="red" stroke-width="1.5"/>
<circle cx="{round(50 + radius*cos(angle*pi/180), 2)}"
cy="{round(50 + radius*sin(angle*pi/180), 2)}"
r="3" fill="red"/>
</svg>
```
Renders: dashed circle + red line + red dot. Two sliders control radius and angle simultaneously.
Example 2: auto-rotation (timer + SVG)
```interact
timer angle range=0..360 step=2 speed=50 loop=true label="Angle"
template svg:
<svg viewBox="0 0 100 100" width="150" height="150">
<g transform="translate(50, 50)">
<circle r="40" fill="none" stroke="#888"/>
<g transform="rotate({round(angle, 0)})">
<line x1="0" y1="0" x2="40" y2="0" stroke="red" stroke-width="2"/>
<circle cx="40" cy="0" r="3" fill="red"/>
</g>
</g>
</svg>
```
Renders: click Play → auto-rotating red line + dot (2° per 50ms tick; full circle in ~9 seconds). Using SVG <g transform="rotate(...)"> is more intuitive than computing cos/sin directly.
Example 3: full spring oscillator simulation
```interact
timer t range=0..10 step=0.05 speed=50 loop=true label="Time"
slider amplitude range=5..40 step=1 default=25 label="Amplitude"
slider period range=0.5..4 step=0.1 default=2 label="Period"
template svg:
<svg viewBox="0 0 200 100" width="400" height="200">
<line x1="20" y1="50" x2="180" y2="50" stroke="#ccc" stroke-dasharray="3,3"/>
<line x1="100" y1="20" x2="100" y2="80" stroke="#aaa" stroke-width="0.5"/>
<text x="100" y="15" text-anchor="middle" font-size="6" fill="#888">Equilibrium</text>
<circle cx="{round(100 + amplitude * sin(2*pi*t/period), 2)}" cy="50" r="6" fill="red"/>
</svg>
```
Renders: horizontal line (ground) + equilibrium marker + red ball doing simple harmonic motion after Play is clicked. Two sliders control amplitude and period; the timer drives time.
Example 4: simplified solar system
```interact
timer t range=0..1000 step=1 speed=50 loop=true label="Time"
template svg:
<svg viewBox="0 0 200 200" width="400" height="400">
<!-- Sun -->
<circle cx="100" cy="100" r="8" fill="orange"/>
<!-- Earth orbit -->
<circle cx="100" cy="100" r="40" fill="none" stroke="#ccc" stroke-width="0.5" stroke-dasharray="2"/>
<circle cx="{round(100 + 40*cos(t*0.1), 2)}" cy="{round(100 + 40*sin(t*0.1), 2)}" r="4" fill="blue"/>
<!-- Mars orbit (longer period) -->
<circle cx="100" cy="100" r="60" fill="none" stroke="#ccc" stroke-width="0.5" stroke-dasharray="2"/>
<circle cx="{round(100 + 60*cos(t*0.05), 2)}" cy="{round(100 + 60*sin(t*0.05), 2)}" r="3" fill="red"/>
<!-- Jupiter orbit (longer period) -->
<circle cx="100" cy="100" r="80" fill="none" stroke="#ccc" stroke-width="0.5" stroke-dasharray="2"/>
<circle cx="{round(100 + 80*cos(t*0.02), 2)}" cy="{round(100 + 80*sin(t*0.02), 2)}" r="6" fill="brown"/>
</svg>
```
Renders: Sun (orange) + Earth (blue) + Mars (red) + Jupiter (brown), each with different orbital periods (Earth fastest, Jupiter slowest) after Play is clicked. The single timer drives all planets in sync.
Plain-text fallback behavior
```interact
slider angle range=0..360 step=1 default=0 label="Angle"
template svg:
<svg viewBox="0 0 100 100">
...
</svg>
```
→ Non-supporting readers show as a code block — readers see the full SVG markup (with {angle} placeholders); anyone with SVG knowledge can imagine the picture.
| Reader | Render |
|---|---|
| Rho MD | Full interactive SVG scene |
| GitHub web | Code block (SVG markup as literal) |
| Obsidian | Same |
| VS Code default preview | Same |
| cat / less | Plain text |
Note: GitHub and the like don't auto-render SVG markup (even if you write
<svg>directly in markdown). It takes Rho's svg template to bring it to life.
Common pitfalls
1. SVG syntax error (missing closing tag)
template svg:
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40"
← ❌ Missing />
</svg>
→ SVG fails to parse; the whole block doesn't render. SVG is strict XML — all self-closing tags need />, all elements need a paired close.
Recommendation: design in an SVG editor (Inkscape / SVG-Edit / Boxy SVG) and tweak the export.
2. Placeholder in wrong position
template svg:
<circle cx={x} cy={y}> ← ❌ Missing quotes
<circle cx="{x}" cy="{y}"> ← ✅
SVG attribute values must be in double quotes — even when substituting numbers.
3. mini DSL doesn't have that function
template svg:
<rect ... transform="rotate({Math.atan2(dy, dx)})"> ← ❌ Math.atan2 doesn't exist
← ✅ use atan2(dy, dx) (mini DSL builtin)
mini DSL isn't JS — Math.X / Number.X don't exist. Use Computed function list directly.
4. Trying to embed HTML / script
template svg:
<svg>
<foreignObject>...</foreignObject> ← ❌ Stripped by sanitizer
<script>...</script> ← ❌ Stripped
<a href="javascript:...">...</a> ← ❌ href cleared
</svg>
→ Rho's safety policy deliberately blocks these. If you need such capabilities, Rho isn't the right tool.
5. No viewBox → render anomaly
template svg:
<svg width="200" height="200"> ← ⚠️ Missing viewBox, scaling unpredictable
<svg viewBox="0 0 200 200" width="200" height="200"> ← ✅ Recommended
Always set viewBox — it defines the internal coordinate system; outer width/height controls actual display size; together they make it scalable.
6. Performance: too many elements
template svg:
<svg>
<!-- 1000 circles re-rendering every frame → browser hangs -->
<circle ... />
<circle ... />
... × 1000
</svg>
→ Element count directly impacts render performance. < 200 elements is most comfortable. For more, use vega-lite or canvas (Rho doesn't support the latter).
7. Jumpy animation (discrete expressions)
template svg:
<circle cx="{floor(t*10)}" cy="50" r="3"/> ← ⚠️ Using floor jumps 10x per second (looks janky)
<circle cx="{round(t*10, 2)}" cy="50" r="3"/> ← ✅ Smooth interpolation
Avoid floor / ceil / round-to-0-decimals in expressions (unless you actually want the discrete effect). round(x, 2) keeps 2 decimals of smooth motion; it's round(x, 0) / floor(x) / ceil(x) that snap to whole numbers.
Recommended SVG learning resources
If you're not familiar with SVG:
- MDN SVG tutorial
- SVG-Edit online editor — design and export markup
- Boxy SVG — desktop SVG editor
Once comfortable, this page covers 90% of what you'll do for Rho animations.
See also
- Interact controls — sliders etc. driving SVG
- Timer — auto-playing SVG animation
- Computed — full mini DSL function list
- Interact + Vega-Lite chart — standard charts (not SVG)
- Shared state (namespace)
- Plain-text fallback principle