A lot of physics in visualization is just glitter with velocity. Semiotic's physics work is more constrained than that. The claim is not "things move, therefore the chart is engaging." The claim is: some data stories have a mechanism, and the mechanism is easier to understand when the chart makes it visible.
That is why the new surface is a frame, not a canvas toy. The bodies are data-bearing objects. The walls and sensors come from chart geometry. The animation has a runtime state. The final positions are still a chart. A reader who never watches the motion should still get the evidence.
Use physics when the movement is the data claim. Sampling, lateness, queueing, routing, threshold crossing, and accumulation can earn motion. A static ranking, a simple time series, or a normal bar chart usually cannot.
Two flagship examples
The first flagship example isWatermarks, Made Physical, a credited remake of the mechanic from flink-watermarks.wtf on top ofEventDropChart. It shows the thing stream processors care about but dashboards often hide: event time and arrival time are different axes of truth. A record can arrive late, a watermark can close a window, and the resulting classification is process state, not just mark color.
The second isPlinko Quantile Dotplot. Posterior samples become semantic tokens through generateTokens, fall through GaltonBoardChart, then settle into a quantile dotplot built from the same tokens. The animation is not a separate illustration. It is the sampling process that produces the final uncertainty display.
stream processWatermarks
Arrival time is the fall order. Event time is the binning system. A watermark is not a decorative line; it is the rule that closes old windows.
- Settled projection
- Event-time windows, on-time counts, late counts, and arrival order.
- Trace
- onTick runtime state, sensor observations, body selection, and conversation-arc edits.
uncertainty processPlinko quantiles
The board is a frequency-frame: each falling body is a posterior draw that keeps its identity when the motion ends.
- Settled projection
- A quantile dotplot laid out from the same posterior-sample tokens.
- Trace
- Scenario, threshold, token count, replay, and selected drop events.
bespoke mechanismCustom routing
PhysicsCustomChart exists for the cases where barriers, proximity sensors, and custom colliders are the point of the chart.
- Settled projection
- Lane totals, proximity sensor states, and selected packet state.
- Trace
- Route pushes, barrier state, sensor transitions, and reader grounding.
The API shape
The chart authoring surface stays ordinary React. You name the data fields that mean time, arrival, value, or category; the HOC builds the physics world from that chart contract.
import { EventDropChart } from "semiotic/physics" <EventDropChart data={events} timeAccessor="eventTime" arrivalAccessor="arrivalTime" windows={{ size: 12 }} watermark={{ delay: 18 }} colorBy="source" frameProps={{ onTick: (result, controls) => setRuntime(controls.snapshot()), onBodyPointerDown: (body) => setSelectedEvent(body?.datum), }} />The important part is the frameProps bridge. It means the simulation is not sealed inside a renderer. Runtime state, observations, and selected bodies can feed readouts, tests, annotations, and AI tooling.
The guardrails
| Rule | What it prevents | What shipped |
|---|
| Settle into a chart | Motion becoming spectacle after the reader loses the thread. | Histogram, dotplot, bar/pile, window table, and lane-state projections. |
| Make chrome physical | Decorative collisions detached from scales and annotations. | Scale-derived bins, walls, floors, thresholds, watermarks, and sensors. |
| Expose the runtime | A demo that looks right but cannot be audited or described. | Reader grounding, observation hooks, selected bodies, and arc events. |
| Export deterministically | A marketing GIF that is unrelated to the simulation being documented. | Server-side frame stepping for SVG frames and animated GIFs. |
These rules are the difference between a physics chart and a particle effect. TheWhen Physics? guide says the same thing as product guidance: pick the mode first. If the movement is not layout, process didactics, or flow texture with a readable fallback, do not use it.
Observable by readers and agents
The examples now opt in to the conversation arc. Change a scenario, adjust the watermark delay, inject a late burst, replay a Plinko board, select a body, or move the threshold: the local arc counter records the render and edit events. That is not analytics by default; it is an in-memory inspection surface so a human or adversarial test agent can verify that the chart's interaction story is visible.
Reader grounding also learned about physics. A chart can now expose simulation parameters, body counts, active sensors, aggregates, and sediment state to the same interrogation layer that describes ordinary charts. That matters because a physics chart has more ways to lie: it can make an animation feel causal when the settled data says otherwise. Grounding gives the assistant and the test harness the boring facts.
Export without pretending
The server path matters for the same reason. An animated GIF of a physics chart should not be a fake sequence made by slicing more rows into the data. The newgeneratePhysicsFrameSVGs andrenderPhysicsToAnimatedGif path steps the actual physics store and captures deterministic frames.
import { renderPhysicsToAnimatedGif } from "semiotic/server" import { buildEventDropPhysics } from "semiotic/physics" const layout = buildEventDropPhysics({ data: events, timeAccessor: "eventTime", arrivalAccessor: "arrivalTime", windows: { size: 12 }, watermark: { delay: 18 }, size: [640, 360], }) const gif = await renderPhysicsToAnimatedGif({ width: 640, height: 360, config: layout.config, initialSpawns: layout.initialSpawns, initialSpawnPacing: layout.initialSpawnPacing, }, { frameCount: 48, fps: 18, })That makes the README animation, the docs example, and the server export part of the same contract. If the simulation changes, the exported frames change with it.
Why this belongs in Semiotic
Physics is stateful integration. It is not a pure layout function. Positions depend on prior positions, collisions, impulses, sensors, sleep, and time. That needs a lifecycle owner: a world, a store, a frame loop, and a way to project moving bodies back into Semiotic's scene vocabulary.
Once it is inside that frame contract, the rest of the library shows up: theming, hit testing, reduced-motion fallback, accessible readouts, server rendering, reader grounding, and the export path. The motion is new. The obligations are not.