Coming from Manim

Motion Script covers the same ground as Manim's graphs, formulas and 3D scenes, through @motionscript/math. The ideas carry over; the way a scene is written does not. This page maps one to the other.

npm install @motionscript/math @motionscript/sdk

What changes

A scene is data, not a program. In Manim, construct runs once and each self.play call renders as it goes. Here a scene is a document: the things in it (nodes) and what happens to them (commands, each placed at a time). Nothing runs in order. A frame is worked out from the time alone, so any frame can be shown without playing the ones before it, and an editor can scrub, cut and export any stretch of it.

A self.play is a command with an at. Where Manim waits for one animation to end before the next starts, a command says when it starts and how long it lasts. Two commands at the same at run together; self.wait(1) is a second with no command in it.

An updater is a binding. Manim keeps a value in a ValueTracker and recomputes what depends on it every frame. Here the value is a document variable, a prop that depends on it is written as a formula ("=[t]"), and variableTo drives it. There is no frame-to-frame state, so the result is the same however you get to a frame.

A coordinate system holds what is drawn on it. axes.plot(...) and axes.c2p(...) become children: a functionPlot placed inside an axes2d is drawn in its graph units, and so is the x/y of any other node put inside it, a point, a label or an image.

LaTeX needs no TeX install. MathJax is bundled, and every glyph of a formula is a path that can be coloured, written on and morphed.

A scene side by side

A dot sliding along a parabola to its lowest point. In Manim:

class Lowest(Scene):
    def construct(self):
        axes = Axes(x_range=[0, 10], y_range=[0, 100, 10])
        curve = axes.plot(lambda x: 2 * (x - 5) ** 2)
        t = ValueTracker(0)
        dot = always_redraw(lambda: Dot(axes.i2gp(t.get_value(), curve)))
        self.add(axes, curve, dot)
        self.play(t.animate.set_value(5))

As a document:

{
  kind: 'composition',
  variables: [{ id: 't', value: 0 }],
  nodes: [
    { id: 'axes', type: 'axes2d', parentId: null, layerOrder: 0,
      props: { width: 760, height: 420, xRange: [0, 10], yRange: [0, 100], yStep: 10 } },
    { id: 'curve', type: 'functionPlot', parentId: 'axes', layerOrder: 0,
      props: { expression: '2(x - 5)^2' } },
    // Inside the plot, a point rides it: its x says where, its height is the curve's.
    { id: 'dot', type: 'plotPoint', parentId: 'curve', layerOrder: 0,
      props: { x: '=[t]' } },
  ],
  commands: [
    { id: 'slide', type: 'variableTo', targetId: 't', at: 0, duration: 1.5, args: { value: 5 } },
  ],
}

Classes

ManimMotion Script
NumberLine, UnitIntervalnumberLine
Axesaxes2d
NumberPlane, ComplexPlanenumberPlane
PolarPlanepolarPlane
ThreeDAxesaxes3d, a 3D graph with its own camera
axes.plot, ParametricFunction, ImplicitFunction, polar graphsfunctionPlot: how the equation is written decides which
axes.plot_line_graph, Polygon on axespointsPlot
Surface, axes.plot_surfacesurfacePlot3d
ParametricFunction in 3DcurvePlot3d
Dot, LabeledDot, axes.i2gpplotPoint (inside a plot, it rides it)
Dot3DplotPoint3d
Arrow, Vector, DoubleArrow, Line on axesvectorArrow; its head is the stroke's endCap
Arrow3DvectorArrow3d
axes.get_areacurveArea
axes.get_riemann_rectanglesriemannSum
TangentLine, axes.get_secant_slope_grouptangentLine (dx above zero for a secant)
axes.get_vertical_line, get_lines_to_pointdropLines
ArrowVectorFieldvectorField
StreamLinesstreamLines
TracedPathtracedPath
MathTex, Texlatex
Matrix, IntegerMatrixlatexMatrix
Brace, BraceLabel, BraceBetweenPointscurlyBrace
Angle, RightAngleangleMark
SurroundingRectangle, Underline, CrossemphasisMark
Graph, DiGraphnetworkGraph
SampleSpacesampleSpace
DecimalNumber, Variablecore's number, bound to a variable
TablerectangularGrid in @motionscript/grids
BarChartbarChart in @motionscript/charts

Animations

ManimMotion Script
Create, DrawBorderThenFillprogressTo (start, end)
WritewriteTo on a latex
FadeIn, FadeOutfadeTo
Transform, ReplacementTransformmorphTo on a plot, a formula or a field
TransformMatchingTexmorphTo on a latex: shared terms travel
Indicatepulse
Wigglewiggle
ShowPassingFlashflash
Circumscribean emphasisMark, drawn on with progressTo
MoveAlongPathmoveTo with a path
Rotate, ScaleInPlacerotateTo, scaleTo
obj.animate.set_colorfillTo, strokeTo; partFillTo for one part of a formula or a graph
ValueTracker.animate.set_valuevariableTo
axes range changesrangeTo
ApplyMatrix, LinearTransformationScenetransformTo on a numberPlane
ApplyPointwiseFunction, prepare_for_nonlinear_transformwarpTo on a numberPlane
graph.change_layoutarrangeTo on a networkGraph
move_camera, begin_ambient_camera_rotationorbitTo on an axes3d, a canvas3d or a camera3d
self.camera.frame.animate.move_to(dot)a camera with follow naming the dot, and zoomTo

Where it differs

  • Angles in equations are radians and trig takes bare names (sin(x)); a name an equation does not know is a value of yours, set in params and animated with paramsTo.
  • 3D graphs are z up and right-handed, as in Manim; the scene itself is y up, so an axes3d's default camera already shows the textbook view.
  • A morph between two equations blends their points, so any kind of curve bends into any other. An implicit curve has no points to blend and changes half way.
  • Lengths that are not graph values are pixels in 2D and scene units in 3D: a point's radius, a stroke's weight, a label's size. They keep their size when the axes' range changes.

Every component above has its own page; the Math overview lists them by group.