Computed — derived values
Use
computed name = expressionto declare a derived variable — auto-recomputes when source variables change. Pull "complex expressions" out of templates so interact blocks stay readable, composable, and chain-derivable.
When to use
- ✅ Same expression referenced multiple times in template (avoid duplication)
- ✅ Multi-step computation (intermediate values are meaningful and worth naming)
- ✅ Conditional categorization (use ternary to map numbers to labels)
- ✅ Reuse across charts / templates (multiple blocks share one derivation)
- ❌ Simple one-off expressions (write directly in template, more direct)
- ❌ Complex business logic / side effects / async (the mini DSL is pure-function — no IO / state mutation)
Basic syntax
\```interact
slider weight 40 120 70 1
slider height 1.4 2.1 1.7 0.01
computed bmi = weight / (height * height)
template stl:
[BMI] -> [{bmi:.1f}]
\```
computed <name> = <expression> declares a derived variable. Expressions evaluate in declaration order.
Renders: drag any slider → bmi recomputes → template updates live.
Expression mini-DSL — full spec
Arithmetic
| Operator | Syntax | Example |
|---|---|---|
| Add / sub / mul / div | + - * / |
a + b * c |
| Modulo | % |
n % 10 |
| Power | ** or pow(a,b) |
2 ** n or pow(2, n) |
Comparison + logical
| Operator | Syntax | Example |
|---|---|---|
| Comparison | == != < > <= >= |
age >= 18 |
| Logical | && || ! |
enabled && !muted |
| Ternary | cond ? a : b |
bmi < 18.5 ? "thin" : "normal" |
Built-in functions
| Category | Functions |
|---|---|
| Basic math | abs(x) sign(x) min(a,b,...) max(a,b,...) |
| Powers / roots | pow(a,b) sqrt(x) exp(x) log(x) log2(x) log10(x) |
| Trigonometry | sin(x) cos(x) tan(x) asin(x) acos(x) atan(x) atan2(y,x) |
| Rounding | floor(x) ceil(x) round(x) trunc(x) |
| Strings | length(s) upper(s) lower(s) substring(s,start,end) |
| Casts | parseFloat(s) parseInt(s) toString(n) |
Constants
| Name | Value |
|---|---|
pi |
3.14159... |
e |
2.71828... |
Not supported (safety design)
- ❌
eval/Functionconstructor - ❌ DOM / window / document access
- ❌
fetch/ network - ❌
setTimeout/ async - ❌
if/elsestatements (use ternary) - ❌ for/while loops (use vega-lite transforms or svg multi-element generation)
- ❌ Function definitions / closures (no
function foo() {...})
Design goal: pure-function expressions, absolutely safe (the reader needs no sandbox), easy for LLMs to generate without errors.
Examples
Example 1: BMI calculation + classification
\```interact
slider weight 40 120 70 1
slider height 1.4 2.1 1.7 0.01
computed bmi = weight / (height * height)
computed category = bmi < 18.5 ? "Underweight"
: bmi < 24 ? "Normal"
: bmi < 28 ? "Overweight"
: "Obese"
template stl:
[BMI] -> [{bmi:.1f}] ::mod(category="{category}")
\```
Renders: BMI number + auto-classified label. category uses nested ternaries to map number to label.
Example 2: compound interest — multi-step chained derivation
\```interact
slider principal 1000 100000 10000 1000
slider rate 0 0.15 0.05 0.01
slider years 1 30 10 1
computed multiplier = pow(1+rate, years)
computed compound = principal * multiplier
computed gain = compound - principal
computed gainPercent = (gain / principal) * 100
template stl:
[Principal] -> [${principal:.0f}]
[Multiplier] -> [{multiplier:.3f}x]
[Final value] -> [${compound:.0f}]
[Net gain] -> [${gain:.0f} ({gainPercent:.1f}%)]
\```
Renders: 4 output lines. multiplier → compound → gain → gainPercent chained derivation; each intermediate value referenced in template.
Example 3: physics — spring oscillator position
\```interact
slider amplitude 0 5 2 0.1
slider frequency 0.1 5 1 0.1
slider phase 0 6.28 0 0.1
slider t 0 10 0 0.1
computed angularFreq = 2 * pi * frequency
computed position = amplitude * sin(angularFreq * t + phase)
computed velocity = amplitude * angularFreq * cos(angularFreq * t + phase)
template stl:
[Position x(t)] -> [{position:.3f} m]
[Velocity v(t)] -> [{velocity:.3f} m/s]
\```
Renders: 4 sliders control spring parameters; 2 lines output position + velocity (based on SHM physics formula). angularFreq = 2πf is the intermediate derivation.
Example 4: toggle-driven conditional branches
\```interact
slider price 0 1000 100 1
toggle isPremium false
toggle hasCoupon false
computed memberDiscount = isPremium ? 0.2 : 0
computed couponDiscount = hasCoupon ? 0.1 : 0
computed totalDiscount = min(memberDiscount + couponDiscount, 0.3)
computed finalPrice = price * (1 - totalDiscount)
template stl:
[Original] -> [${price}]
[Member off] -> [{(memberDiscount*100):.0f}%]
[Coupon off] -> [{(couponDiscount*100):.0f}%]
[Total off] -> [{(totalDiscount*100):.0f}% (≤30% cap)]
[Final price] -> [${finalPrice:.2f}]
\```
Renders: toggles switch member / coupon; totalDiscount uses min(...) to apply 30% cap. Logic stays in declarations, not imperative.
Plain-text fallback behavior
computed declarations are fully visible in plain-text readers:
\```interact
slider weight 40 120 70 1
slider height 1.4 2.1 1.7 0.01
computed bmi = weight / (height * height)
template stl:
[BMI] -> [{bmi:.1f}]
\```
→ Readers see computed bmi = weight / (height * height) and fully understand the derivation. Plain text doesn't evaluate, but the expression itself is well-documented.
Common pitfalls
1. Circular dependency
\```interact
computed a = b + 1 ← ❌ Mutual dependency
computed b = a + 1
template stl:
[a] -> [{a}]
\```
→ Rho's topological sort fails. Computeds can't form a cycle. For "previous value + 1" patterns, use button self-reference (see Interact controls Example 3).
2. computed references undeclared variable
\```interact
slider x 0 10 5 1
computed y = x + z ← ❌ z not declared
template stl:
[y] -> [{y}]
\```
→ Expression throws "undefined z". Declare all variables (slider/input/computed) first.
3. Expression syntax errors
computed bmi = weight / height ** 2 ← ⚠️ Precedence confusion; actually weight / (height ** 2)
computed bmi = (weight / height) ** 2 ← ❌ Wrong formula
computed bmi = weight / (height * height) ← ✅ Recommended: explicit parens
computed bmi = weight / pow(height, 2) ← ✅ Recommended: use pow function
Use parentheses to express precedence explicitly — don't rely on implicit precedence; avoids reader (and LLM) miscomputation.
4. Ternary nesting too deep
computed grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : score >= 60 ? "D" : "F"
3-level ternary is near readability limit. For 4+ levels, use multi-line format:
computed grade = score >= 90 ? "A"
: score >= 80 ? "B"
: score >= 70 ? "C"
: score >= 60 ? "D"
: "F"
Or split into multiple computeds:
computed isHigh = score >= 90
computed isMid = score >= 70 && score < 90
computed grade = isHigh ? "A" : isMid ? "C" : "F"
5. Expression with string operations
input city "Shanghai"
computed greeting = "Hello, " + city + "!" ← ⚠️ Some implementations support, some don't
mini DSL string concatenation support varies by reader implementation. Safest: write {city} interpolation directly in template.
6. Floating-point precision loss
computed total = 0.1 + 0.2 ← May produce 0.30000000000000004
template stl:
[Result] -> [{total}]
→ Floats are IEEE 754 — finite precision. For money / ratios, force precision via format spec: {total:.2f} → 0.30.
7. Too many computeds
\```interact
slider a 0 10 5 1
computed b = a * 2
computed c = b + 1
computed d = c / 3
computed e = pow(d, 2)
computed f = sqrt(e)
... (15 computeds)
\```
→ Maintenance nightmare. Redesign: combine multiple computeds into one expression (if intermediates aren't referenced); or split across multiple interact blocks (sharing via namespace).
Computed vs template expression — choose
| Scenario | Use |
|---|---|
| One-off simple expression | Write {a + b} directly in template |
| Same expression in template ≥ 2 times | Extract as computed |
| Multi-step derivation (intermediates have meaningful names) | Multiple computeds chained |
| Reused across templates / charts | computed |
See also
- Interact controls — slider / input / select / toggle / button
- Interact text template — template syntax
- Interact + Vega-Lite chart — charts can reference computeds too
- Timer — auto-play (also drives computeds)
- Shared state (namespace)
- Plain-text fallback principle