Science
Plot 2D
Draws a 2D scene with plain SVG: lines, points, vectors and text, with an optional play bar. It does no math and has no dependencies; it only draws the data it is given.
A scene is a plain object: bounds (the visible math window), duration in seconds (0 for a static plot) and a pure frame(t) function that returns the items to draw. Hand-write one, or take it from @indoku/science (for example projectileScene() or waveScene()); the shape is the same.
Import
import { MathRenderer, Plot2D } from "@indoku/ui"import type { Scene2DLike } from "@indoku/ui"const renderLatex = (latex: string) => <MathRenderer latex={latex} />Anatomy
<Plot2D scene={{ kind: "2d", bounds, duration, loop?, latex?, frame(t) }} />frame(t) returns Item2D[]: { t: "line", pts: [x, y][], color?, width?, dash? } { t: "point", x, y, r?, color?, label? } { t: "vector", x, y, dx, dy, color?, label? } { t: "text", x, y, text, color? }colors: "accent" | "muted" | "ok" | "warn" | "danger" | any CSS colorExamples
Animated scene
An animated scene plays on its own and shows a play bar with a scrubber and speed buttons. Animation starts paused when the user prefers reduced motion.
function Plot2DBasic() { return <Plot2D scene={wave} />}Controlled time
Pass t to drive the plot from your own state (a slider, a lesson step, scroll position). The play bar is hidden.
function Plot2DControlled() { const [t, setT] = useState(0.5) return ( <div> <input type="range" min={0} max={2} step={0.01} value={t} onChange={(e) => setT(parseFloat(e.target.value))} style={{ width: "100%", marginBottom: 8 }} /> <Plot2D scene={wave} t={t} /> </div> )}Vectors, dashes and formulas
Vectors point in data coordinates. renderLatex receives each string in scene.latex. Pass a renderLatex built on MathRenderer to typeset them with KaTeX.
function Plot2DVectors() { return <Plot2D scene={projectile} aspect={2.2} renderLatex={renderLatex} />}Static scene
duration: 0 makes a static plot with no play bar. Turn the grid off with grid={false}.
function Plot2DStatic() { const parabola: Scene2DLike = { kind: "2d", bounds: { xMin: -1, xMax: 4, yMin: -1, yMax: 3 }, duration: 0, frame: () => [ { t: "line", pts: Array.from({ length: 101 }, (_, i) => [-1 + i * 0.05, (-1 + i * 0.05) ** 2 - 3 * (-1 + i * 0.05) + 2] as [number, number]), color: "accent", width: 2 }, { t: "point", x: 1, y: 0, color: "ok", label: "x₁" }, { t: "point", x: 2, y: 0, color: "ok", label: "x₂" }, { t: "point", x: 1.5, y: -0.25, color: "warn", label: "vertex" }, ], } return <Plot2D scene={parabola} grid={false} />}Props
Plot2D
| Prop | Type | Default | Description |
|---|---|---|---|
scene* | Scene2DLike | – | The scene to draw: bounds, duration and a pure frame(t) function. |
t | number | – | Controlled time in seconds. Omit to let the component animate itself. |
controls | boolean | true | Show the play bar for animated scenes (only when t is not set). |
aspect | number | 16 / 9 | Width divided by height of the plot area. |
grid | boolean | true | Draw the background grid. |
renderLatex | (latex: string) => ReactNode | – | Renders each string of scene.latex below the plot. Use (latex) => <MathRenderer latex={latex} />, or your own renderer. Omit to hide them. |
* required. Style props and HTML attributes are accepted as well.
Accessibility
- The SVG has
role="img"and usesscene.titleas its label. - The play bar buttons and the time slider are real form controls with labels, so they work with the keyboard.
- Autoplay is off when
prefers-reduced-motionis set; the user can still press Play or drag the scrubber.
Notes
- Keep
frame(t)pure and cheap: it runs on every animation frame. Build large constant data (like a full trajectory) outsideframe, or once per scene. - Keep the
sceneobject stable (module constant oruseMemo). A new object resets the animation clock. - Axes are not equal-scaled. Set
aspectto match yourboundswhen angles matter (a projectile, a circle).
Created by @indokudev