SMIL Animation

kUML can render any state machine or BPMN process diagram as an animated SVG — a single file that plays back the model’s execution trace in the browser or any SVG viewer. No JavaScript, no external assets: the animation is pure SMIL, embedded directly in the SVG.

Quick start

# 1. Run the state machine and capture the trace
kuml simulate order-lifecycle.kuml.kts \
    --events events.json \
    --output order-lifecycle.trace.json

# 2. Render the animated SVG
kuml render order-lifecycle.kuml.kts \
    --animated --trace order-lifecycle.trace.json \
    --format svg \
    --output order-lifecycle-animated.svg

Open the resulting file in any modern browser — the token moves through the diagram according to the captured trace.

The same workflow works for BPMN:

kuml simulate membership-process.kuml.kts \
    --events member-events.json \
    --output membership.trace.json

kuml render membership-process.kuml.kts \
    --animated --trace membership.trace.json \
    --format svg

Trace format (kuml.trace.v1)

The trace file is a JSON document produced by kuml simulate. It is the single source of truth for the animation — the renderer reads the trace and derives all timing from it without running the machine again.

{
  "schema": "kuml.trace.v1",
  "machine": "Order",
  "entries": [
    {"type": "Initialised",    "vertex": "start"},
    {"type": "Transitioned",   "trigger": null, "from": "start", "to": "draft"},
    {"type": "Entered",        "vertex": "draft"},
    {"type": "EventReceived",  "event": {"name": "confirm"}},
    {"type": "Transitioned",   "trigger": "confirm", "from": "draft", "to": "confirmed"},
    {"type": "Exited",         "vertex": "draft"},
    {"type": "Entered",        "vertex": "confirmed"},
    {"type": "Terminated"}
  ]
}

For BPMN, the relevant entry types are TokenPlaced, TokenConsumed, and DecisionTaken. These map to token circle animations along sequence-flow paths.

Read traces programmatically with:

val traceFile = KumlRuntimeJson.decodeFromString(TraceFile.serializer(), json)

Animation elements

Diagram type Animation elements

UML State Machine

Highlight overlay <rect> per active state (id: smil-stm-hl-<vertexId>); <path> overlay per fired transition; opacity-pulse reveals each element at the correct instant.

SysML 2 STM

Same overlay scheme as UML STM — StmSmilRenderer handles both metamodels.

UML Activity

Token <circle> elements travel along action-flow edges using <animateMotion>. Fork/join and decision nodes animate in sequence.

BPMN Process

Token <circle> elements follow sequence-flow <path>`s via `<animateMotion>. Gateway highlights use <animate attributeName="fill">. Task activation uses a stroke-width pulse; start/end events dim on completion.

Per ADR-0014, <animateColor> is never emitted — <animate attributeName="fill"> is used instead (deprecated in SVG 1.2, removed in SVG 2.0).

Speed control

Scale the entire timeline with --speed:

kuml render machine.kuml.kts \
    --animated --trace trace.json \
    --speed 2.0 \
    --output fast.svg

SpeedFactor is type-safe — values < 0.1 or > 20.0 are rejected with a clear error. The default speed factor is 1.0.

Exporting to APNG, WebP, or MP4

An animated SVG can be transcoded to a self-contained animated file with --format:

kuml render order-lifecycle.kuml.kts \
    --animated --trace order-lifecycle.trace.json \
    --format apng

kuml render order-lifecycle.kuml.kts \
    --animated --trace order-lifecycle.trace.json \
    --format webp

kuml render order-lifecycle.kuml.kts \
    --animated --trace order-lifecycle.trace.json \
    --format mp4
Format Requirements

apng

None — pure-JVM frame assembly.

webp

img2webp (libwebp) or ffmpeg on PATH.

mp4

ffmpeg on PATH. Encoded as H.264/yuv420p for broad playback compatibility.

MP4/H.264 has no standard alpha-channel support. --format=mp4 always renders against an opaque background. Use apng or webp when a transparent background is required.

Frame count and duration are capped (FrameBudget) to prevent runaway output size; see the CLI --speed option above for compressing a long trace into a shorter animation instead.

Static fallback

When --animated is omitted, or when the trace produces no animations, the renderer falls back to a static SVG that is byte-identical to the output of kuml render without --animated. This guarantees that switching animation on/off does not change the diagram layout.

You can also strip SMIL elements from an existing animated SVG:

import dev.kuml.io.smil.SmilEmitter
import dev.kuml.io.smil.StaticSnapshotMode

val staticSvg = SmilEmitter.inject(animatedSvg, timeline = null, mode = StaticSnapshotMode.STRIPPED)

STRIPPED removes every <animate*> and <set > element and suppresses injection of new ones — safe for PNG rendering pipelines.

Embedding the renderers

import dev.kuml.io.smil.BpmnSmilRenderer
import dev.kuml.io.smil.StmSmilRenderer

// BPMN animated SVG
val bpmnSvg = BpmnSmilRenderer().render(bpmnDiagram, traceFile, speedFactor = 1.0)

// UML / SysML 2 State Machine animated SVG
val stmSvg = StmSmilRenderer().render(stmDiagram, traceFile, speedFactor = 1.0)

Both renderers are in kuml-io/kuml-render-smil (dev.kuml:kuml-render-smil).

Browser compatibility

Animated kUML SVGs play back in all evergreen browsers (Chrome 88+, Firefox 63+, Safari 13.1+). The <animateMotion> and <animate> elements used are part of the SVG/SMIL subset that all major browsers support. Internet Explorer is not supported.

Full cross-browser support (Chrome and Safari in addition to Firefox) requires v0.20.1 or later. Earlier releases used the SVG-2 bare href attribute on SMIL elements; browser SMIL implementations are based on SVG 1.1 and require xlink:href together with the xmlns:xlink namespace declaration on the <svg> root. Firefox played animations correctly in all versions; Chrome and Safari silently ignored the SMIL elements until the fix in v0.20.1 (SmilEmitter now emits xlink:href and injects xmlns:xlink into the root tag automatically).

See also