Interact + Vega-Lite — live chart
Use
template vega-lite:to wire Interact controls to a Vega-Lite chart — drag a slider / flip a toggle, chart redraws live. Observable / Streamlit-grade experience, fully declarative, zero setup.
When to use
- ✅ Parameter-driven curves / distributions (drag a slider to change function parameters; watch the curve deform)
- ✅ Data exploration (toggle dimensions / aggregations / color encoding)
- ✅ Teaching visualizations (trigonometry / probability density / physics simulations)
- ✅ Finance / engineering / data science (anything that needs "tweak parameter → see trend")
- ❌ Minimal result display (use text template)
- ❌ Static charts (use
```chartdirectly, no interact wrapper needed) - ❌ Need complex D3 customization → vega-lite has expressiveness limits; use SVG scenes
Basic syntax
```interact
slider freq range=1..10 step=0.1 default=3 label="Frequency"
template vega-lite:
{
"data": {"sequence": {"start": 0, "stop": 6.28, "step": 0.05, "as": "t"}},
"transform": [{"calculate": "sin({freq} * datum.t)", "as": "y"}],
"mark": "line",
"encoding": {
"x": {"field": "t", "type": "quantitative"},
"y": {"field": "y", "type": "quantitative"}
}
}
```
template vega-lite: is followed by a Vega-Lite JSON spec — Rho replaces {var} with control values, then renders the spec directly. Vega-Lite is built into Rho MD — there's nothing to install; the reader just opens the document.
Renders: slider + chart. Drag freq → sin({freq} * t) recomputes → curve deforms.
Where placeholders live in vega-lite spec
{var} can appear in any JSON string value, replaced live like everywhere else:
{
"transform": [
{"calculate": "{amplitude} * sin({frequency} * datum.t)", "as": "y"}
],
"encoding": {
"y": {"field": "y", "type": "quantitative", "scale": {"domain": [-{maxY}, {maxY}]}}
}
}
Replacement happens before JSON parsing, so {var} works in numeric fields (domains, scale bounds) or string fields (color hex / titles).
⚠️ Numeric fields: use
{var}(no quotes) — replacement yields a bare number. String fields: use"{var}"(with quotes) — replacement yields a string.
Examples
Example 1: sine wave + frequency / amplitude
```interact
slider freq range=0.5..5 step=0.1 default=1 label="Frequency"
slider amp range=0.1..2 step=0.1 default=1 label="Amplitude"
template vega-lite:
{
"width": 400,
"height": 200,
"data": {"sequence": {"start": 0, "stop": 6.28, "step": 0.05, "as": "t"}},
"transform": [
{"calculate": "{amp} * sin({freq} * datum.t)", "as": "y"}
],
"mark": "line",
"encoding": {
"x": {"field": "t", "type": "quantitative", "title": "Time t"},
"y": {"field": "y", "type": "quantitative", "title": "Amplitude", "scale": {"domain": [-2, 2]}}
}
}
```
Renders: two sliders + sine wave chart. Change amp to scale amplitude; change freq to alter wave density. {amp} {freq} get substituted in calculate.
Example 2: compound interest growth curve
```interact
slider principal range=1000..100000 step=1000 default=10000 label="Principal"
slider rate range=0..0.15 step=0.01 default=0.05 label="Annual rate"
slider years range=5..50 step=1 default=20 label="Years"
template vega-lite:
{
"width": 500,
"height": 250,
"data": {"sequence": {"start": 0, "stop": {years}, "step": 1, "as": "year"}},
"transform": [
{"calculate": "{principal} * pow(1+{rate}, datum.year)", "as": "value"}
],
"mark": {"type": "line", "point": true},
"encoding": {
"x": {"field": "year", "type": "quantitative", "title": "Year"},
"y": {"field": "value", "type": "quantitative", "title": "Amount ($)"}
}
}
```
Renders: 3 sliders + compound interest curve; {years} in sequence's stop → x-axis range tracks the years slider.
Example 3: probability density (normal distribution)
```interact
slider mean range=-5..5 step=0.1 default=0 label="Mean"
slider stddev range=0.1..3 step=0.1 default=1 label="Std dev"
template vega-lite:
{
"width": 500,
"height": 250,
"data": {"sequence": {"start": -10, "stop": 10, "step": 0.1, "as": "x"}},
"transform": [
{"calculate": "(1 / ({stddev} * sqrt(2 * 3.14159))) * exp(-pow(datum.x - {mean}, 2) / (2 * pow({stddev}, 2)))", "as": "p"}
],
"mark": "area",
"encoding": {
"x": {"field": "x", "type": "quantitative", "title": "x"},
"y": {"field": "p", "type": "quantitative", "title": "P(x)"}
}
}
```
Renders: drag mean to shift left/right, drag stddev to widen/narrow.
Example 4: toggle to switch chart type
```interact
toggle showArea default=false label="Show as area"
template vega-lite:
{
"data": {
"values": [
{"month": "Jan", "v": 100},
{"month": "Feb", "v": 130},
{"month": "Mar", "v": 95},
{"month": "Apr", "v": 180},
{"month": "May", "v": 220}
]
},
"mark": "{showArea == 'true' ? 'area' : 'line'}",
"encoding": {
"x": {"field": "month", "type": "ordinal"},
"y": {"field": "v", "type": "quantitative"}
}
}
```
Renders: toggle switches between line and area. toggle yields the string "true"/"false", so the ternary compares showArea == 'true' and returns single-quoted strings ('area'/'line') — the placeholder sits inside an already-double-quoted "mark": "..." value, so string literals inside {} must use single quotes or they'd close the outer quote early.
Plain-text fallback behavior
```interact with template vega-lite: in non-supporting readers:
```interact
slider freq range=1..10 step=0.1 default=3 label="Frequency"
template vega-lite:
{
... full vega-lite spec ...
}
```
→ shows as a code block — readers see sliders + the full vega-lite spec (with {var} placeholders). Experienced readers can guess it's a chart; ordinary readers see structured data.
Per-reader breakdown:
| Reader | Render |
|---|---|
| Rho MD | Full interactive chart |
| GitHub web | Code block (full spec visible) |
| Obsidian | Same |
| VS Code default preview | Same |
| cat / less | Plain text |
Common pitfalls
1. JSON syntax error
template vega-lite:
{
"mark": "line"
"encoding": { ... } ← ❌ Missing comma
}
→ the spec fails to parse; chart doesn't render. vega-lite spec is strict JSON — double-quote keys, comma between values, strict quoting.
Recommendation: write the spec in an external JSON editor (VS Code) or use vega-lite Editor (https://vega.github.io/editor/) to validate.
2. Placeholder in wrong position
template vega-lite:
{
"mark": {freq}, ← ❌ {freq} replaced with number 3, but field needs string "line"
...
}
→ Replacement yields invalid JSON. Decide whether {var} needs quotes by context.
3. Numeric field with quotes by mistake
template vega-lite:
{
"encoding": {
"y": {"scale": {"domain": [-"{maxY}", "{maxY}"]}} ← ❌ Replacement is string "10", scale.domain wants number
}
}
→ Change to [-{maxY}, {maxY}] (no quotes).
4. transform.calculate uses different syntax than mini DSL
template vega-lite:
{
"transform": [
{"calculate": "if (datum.x > 0) datum.x else 0", "as": "y"} ← ❌ statement syntax isn't allowed
]
}
→ Use the ternary cond ? a : b, or the if(cond, a, b) function — see Computed for the full function list. Both are part of the same mini DSL used everywhere else in interact.
5. data.sequence too large
"data": {"sequence": {"start": 0, "stop": 1000000, "step": 0.001, "as": "x"}}
→ 1 billion data points → browser locks up. Keep points < 5000. Curves look smooth at < 500 points.
6. Multiple charts sharing data
```interact
namespace finance
slider rate range=0..0.15 step=0.01 default=0.05 label="Rate"
```
```interact
namespace finance
template vega-lite:
{ ... uses {rate} ... }
```
```interact
namespace finance
template vega-lite:
{ ... uses {rate} ... }
```
✅ Legal + recommended — multiple charts share the same sliders. namespace <name> is its own spec line, declared on the first line of the block (not a fence argument on the ```interact line). See Shared state.
Vega-Lite spec — common cheatsheet
Line chart
{
"data": {...},
"mark": "line",
"encoding": {
"x": {"field": "x", "type": "quantitative"},
"y": {"field": "y", "type": "quantitative"}
}
}
Bar chart
{
"data": {...},
"mark": "bar",
"encoding": {
"x": {"field": "category", "type": "ordinal"},
"y": {"field": "value", "type": "quantitative"}
}
}
Scatter plot
{
"data": {...},
"mark": "circle",
"encoding": {
"x": {"field": "x", "type": "quantitative"},
"y": {"field": "y", "type": "quantitative"},
"size": {"field": "z", "type": "quantitative"}
}
}
Parametric curve (most common)
{
"data": {"sequence": {"start": 0, "stop": 10, "step": 0.1, "as": "x"}},
"transform": [{"calculate": "...{your_expr}...", "as": "y"}],
"mark": "line",
"encoding": {
"x": {"field": "x"},
"y": {"field": "y", "type": "quantitative"}
}
}
See Vega-Lite full docs.
See also
- Interact controls — slider / input / select / toggle
- Interact text template — text template
- Computed — charts can reference computeds too
- Timer — auto-play the chart (animation)
- SVG scenes — graphics with more freedom than charts
- Shared state (namespace) — share sliders across charts
- Plain-text fallback principle