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 ```chart directly, 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

Open in Rho MD →