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

Once comfortable, this page covers 90% of what you'll do for Rho animations.


See also

Open in Rho MD →