# Geomatic — Full LLM Reference > Geomatic is a declarative, canvas-based geometry and math editor built into TinyVolt (https://www.tinyvolt.com/geomatic). Users write commands to create geometric figures, perform computations, animate them, and build interactive articles. > > This file contains everything an LLM needs to generate valid Geomatic macros or interactive articles. It combines the function reference, macro examples, and article examples. ## How to use this context To generate a **macro**: produce a JSON array of `{ "macro": "name", "commands": [...] }` objects. Each command is a string in the form `"varName = \\function arg1 arg2"` or `"\\function arg1 arg2"`. Commands execute sequentially; each output variable is available as an argument to later commands. No nesting — one operation per line. To generate an **interactive article**: produce extended markdown where inline commands are written as `{description}(command)`. Commands collapsible under `####` headings have no visual output. Sliders and circular sliders use `::slider[values, target]` / `::circular-slider[values, target]` syntax. **The set of commands is closed.** Use only the `\keyword`s defined in this document (see the Function Reference). Never invent, guess, or substitute "similar"-sounding commands — if a capability is not listed here, it does not exist unless the user explicitly provides it. Identifiers (assignment names, command keywords, and node references) must start with a letter and contain only letters, digits, and hyphens — **never underscores**. Only `\text` accepts a quoted string and `${...}` interpolation; every other argument is an identifier, a number, or a `node.field` access. --- # Function Reference Reference documentation for all available Geomatic functions and their parameters. ## Basic Figures ### \arc **Description**: Create an arc with center, radius, and start/end angles. **Signature**: `\arc center radius startAngle endAngle` **Input types**: - `center`: Point (default: p0) - `radius`: Scalar (default: 2) - `startAngle`: Scalar (default: 0) - `endAngle`: Scalar (default: 90) **Output type**: Arc **Auxiliary nodes**: None ### \bezier-cubic **Description**: Create a cubic Bezier curve from two endpoints and two control points. **Signature**: `\bezier-cubic p1 control1 control2 p2` **Input types**: - `p1`: Point - `control1`: Point - `control2`: Point - `p2`: Point **Output type**: BezierCubic **Auxiliary nodes**: None ### \bezier-quadratic **Description**: Create a quadratic Bezier curve from two endpoints and a control point. **Signature**: `\bezier-quadratic p1 control p2` **Input types**: - `p1`: Point - `control`: Point - `p2`: Point **Output type**: BezierQuadratic **Auxiliary nodes**: None ### \circle **Description**: Create a circle with a center point and radius. **Signature**: `\circle center radius` **Input types**: - `center`: Point (default: p0) - `radius`: Scalar (default: 2) **Output type**: Circle **Auxiliary nodes**: None ### \ellipse **Description**: Create an ellipse with center, radii, and rotation. **Signature**: `\ellipse center radiusX radiusY rotation` **Input types**: - `center`: Point (default: p0) - `radiusX`: Scalar (default: 3) - `radiusY`: Scalar (default: 2) - `rotation`: Scalar (default: 0) **Output type**: Ellipse **Auxiliary nodes**: None ### \ellipse-from-foci **Description**: Create an ellipse from two foci and a string length. **Signature**: `\ellipse-from-foci focus1 focus2 stringLength` **Input types**: - `focus1`: Point - `focus2`: Point - `stringLength`: Scalar **Output type**: Ellipse **Auxiliary nodes**: center, radiusX, radiusY, rotation ### \line **Description**: Create a line from two points. **Signature**: `\line point1 point2` **Input types**: - `point1`: Point - `point2`: Point **Output type**: Line **Auxiliary nodes**: None ### \point **Description**: Create a point from x and y coordinates. **Signature**: `\point x y` **Input types**: - `x`: Scalar (default: 0) - `y`: Scalar (default: 0) **Output type**: Point **Auxiliary nodes**: None ### \scalar **Description**: Create a scalar value from another scalar. **Signature**: `\scalar value` **Input types**: - `value`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \text **Description**: Create a text node with a string value. **Signature**: `\text value` **Input types**: - `value`: Text **Output type**: Text **Auxiliary nodes**: None ### \triangle **Description**: Create a triangle from three vertex points. **Signature**: `\triangle vertex1 vertex2 vertex3` **Input types**: - `vertex1`: Point - `vertex2`: Point - `vertex3`: Point **Output type**: Triangle **Auxiliary nodes**: l_0, l_1, l_2 (Line nodes) ## Planar Geometry ### \angle **Description**: Calculate the angle at a vertex formed by three points. **Signature**: `\angle point1 vertex point3` **Input types**: - `point1`: Point - `vertex`: Point - `point3`: Point **Output type**: Scalar **Auxiliary nodes**: None ### \area-circle **Description**: Calculate the area of a circle. **Signature**: `\area-circle circle` **Input types**: - `circle`: Circle **Output type**: Scalar **Auxiliary nodes**: None ### \area-triangle **Description**: Calculate the area of a triangle. **Signature**: `\area-triangle triangle` **Input types**: - `triangle`: Triangle **Output type**: Scalar **Auxiliary nodes**: None ### \bisect-angle **Description**: Calculate the angle bisector of two lines that share a point. **Signature**: `\bisect-angle line1 line2` **Input types**: - `line1`: Line - `line2`: Line **Output type**: Line **Auxiliary nodes**: midPoint (Point node) ### \centroid **Description**: Calculate the centroid (geometric center) of a triangle. **Signature**: `\centroid triangle` **Input types**: - `triangle`: Triangle **Output type**: Point **Auxiliary nodes**: None ### \circumcenter **Description**: Calculate the circumcenter of a triangle (center of circumscribed circle). **Signature**: `\circumcenter triangle` **Input types**: - `triangle`: Triangle **Output type**: Point **Auxiliary nodes**: None ### \distance **Description**: Calculate the Euclidean distance between two points. **Signature**: `\distance point1 point2` **Input types**: - `point1`: Point - `point2`: Point (default: p0) **Output type**: Scalar **Auxiliary nodes**: None ### \incenter **Description**: Calculate the incenter of a triangle (center of inscribed circle). **Signature**: `\incenter triangle` **Input types**: - `triangle`: Triangle **Output type**: Point **Auxiliary nodes**: None ### \mid-point **Description**: Calculate the midpoint between two points. **Signature**: `\mid-point point1 point2` **Input types**: - `point1`: Point - `point2`: Point **Output type**: Point **Auxiliary nodes**: None ### \project-point **Description**: Project a point onto a line (find the closest point on the line). **Signature**: `\project-point point line` **Input types**: - `point`: Point - `line`: Line **Output type**: Point **Auxiliary nodes**: None ### \reflect-point **Description**: Reflect a point across a line. **Signature**: `\reflect-point point line` **Input types**: - `point`: Point - `line`: Line **Output type**: Point **Auxiliary nodes**: None ### \slope-of-line **Description**: Calculate the slope angle of a line (angle between 0 and 2PI). **Signature**: `\slope-of-line line` **Input types**: - `line`: Line **Output type**: Scalar **Auxiliary nodes**: None ## Intersections ### \intersection-circle-circle **Description**: Calculate the intersection points of two circles. **Signature**: `\intersection-circle-circle circle1 circle2` **Input types**: - `circle1`: Circle - `circle2`: Circle **Output type**: Array **Auxiliary nodes**: p0, p1 (Point nodes) ### \intersection-line-bezier-quadratic **Description**: Calculate the intersection points of a line and a quadratic Bezier curve. **Signature**: `\intersection-line-bezier-quadratic line bezier` **Input types**: - `line`: Line - `bezier`: BezierQuadratic **Output type**: Array **Auxiliary nodes**: p0, p1... (Point nodes) ### \intersection-line-circle **Description**: Calculate the intersection points of a line and a circle. **Signature**: `\intersection-line-circle line circle` **Input types**: - `line`: Line - `circle`: Circle **Output type**: Array **Auxiliary nodes**: p0, p1 (Point nodes) ### \intersection-line-ellipse **Description**: Calculate the intersection points of a line and an ellipse. **Signature**: `\intersection-line-ellipse line ellipse` **Input types**: - `line`: Line - `ellipse`: Ellipse **Output type**: Array **Auxiliary nodes**: p0, p1 (Point nodes) ### \intersection-line-line **Description**: Calculate the intersection point of two lines. **Signature**: `\intersection-line-line line1 line2` **Input types**: - `line1`: Line - `line2`: Line **Output type**: Point **Auxiliary nodes**: None ## Curves & Plotting ### \clear-trail **Description**: Clear the accumulated history of a trail node. **Signature**: `\clear-trail trailId` **Input types**: - `trailId`: Any **Output type**: Dummy **Auxiliary nodes**: None ### \evaluate-polynomial **Description**: Evaluate a polynomial at a given x. **Signature**: `\evaluate-polynomial polynomial x` **Input types**: - `polynomial`: Polynomial - `x`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \plot **Description**: Plot y vs x over the visible canvas range. **Signature**: `\plot x y` **Input types**: - `x`: Scalar - `y`: Scalar **Output type**: Plot **Auxiliary nodes**: SVG path data ### \plot-inverse **Description**: Plot the output along x while sweeping the independent variable down the y-axis (e.g. x = f(y)) **Signature**: `\plot-inverse x y` **Input types**: - `x`: Scalar - `y`: Scalar **Output type**: Plot **Auxiliary nodes**: None ### \polynomial **Description**: Create a polynomial function from coefficients. **Signature**: `\polynomial a0...` **Input types**: - `a0`: Scalar (variadic) **Output type**: Polynomial **Auxiliary nodes**: None ### \trail **Description**: Track the path of a point as its parameters change. **Signature**: `\trail pointId...` **Input types**: - `pointId`: Any (variadic) **Output type**: Trail **Auxiliary nodes**: accumulated history points ## Polygons ### \convex-hull **Description**: Calculate the convex hull of a set of points using the Graham scan algorithm. **Signature**: `\convex-hull point1 point2...` **Input types**: - `point1`: Point - `point2`: Point (variadic) **Output type**: Polygon **Auxiliary nodes**: l_0...l_n (Line nodes) ### \polygon **Description**: Create a polygon from three or more vertex points. **Signature**: `\polygon vertex1 vertex2 vertex3...` **Input types**: - `vertex1`: Point - `vertex2`: Point - `vertex3`: Point (variadic) **Output type**: Polygon **Auxiliary nodes**: l_0...l_n (Line nodes) ### \polygon-from-side **Description**: Create a regular polygon from two points forming one side and the number of sides. **Signature**: `\polygon-from-side a b n` **Input types**: - `a`: Point - `b`: Point - `n`: Scalar (default: 6) **Output type**: Polygon **Auxiliary nodes**: p_2...p_(n-1) (Point nodes), l_0...l_(n-1) (Line nodes) ### \polyline **Description**: **Signature**: `\polyline point1 point2...` **Input types**: - `point1`: Point - `point2`: Point (variadic) **Output type**: Array **Auxiliary nodes**: None ### \rectangle **Description**: Create a rectangle from a bottom-left corner, width, height, and rotation angle. **Signature**: `\rectangle bottomLeft width height angle` **Input types**: - `bottomLeft`: Point (default: p0) - `width`: Scalar (default: 3) - `height`: Scalar (default: 2) - `angle`: Scalar (default: 0) **Output type**: Polygon **Auxiliary nodes**: p1, p2, p3 (Point nodes), l_0...l_3 (Line nodes) ### \regular-polygon **Description**: Create a regular polygon with center, radius, number of vertices, and start angle. **Signature**: `\regular-polygon center radius numVertices startAngle` **Input types**: - `center`: Point (default: p0) - `radius`: Scalar (default: 2) - `numVertices`: Scalar (default: 6) - `startAngle`: Scalar (default: 0) **Output type**: RegularPolygon **Auxiliary nodes**: p_0...p_n (Point nodes), l_0...l_n (Line nodes) ### \square **Description**: Create a square from a bottom-left corner, side length, and rotation angle. **Signature**: `\square bottomLeft side angle` **Input types**: - `bottomLeft`: Point (default: p0) - `side`: Scalar (default: 2) - `angle`: Scalar (default: 0) **Output type**: Polygon **Auxiliary nodes**: p1, p2, p3 (Point nodes), l_0...l_3 (Line nodes) ## Tangents ### \tangent **Description**: Draw a tangent line to a curve at a given x coordinate. **Signature**: `\tangent curve x length` **Input types**: - `curve`: Any - `x`: Scalar - `length`: Scalar (default: 2) **Output type**: Line **Auxiliary nodes**: p1, p2 (endpoints of tangent line) ## Scalar Functions ### \acos **Description**: Calculate arccosine of a scalar value (returns radians). **Signature**: `\acos a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \asin **Description**: Calculate arcsine of a scalar value (returns radians). **Signature**: `\asin a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \atan **Description**: Calculate arctangent of a scalar value (returns radians). **Signature**: `\atan a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \atan2 **Description**: Calculate arctangent of y/x (returns radians). **Signature**: `\atan2 a b` **Input types**: - `a`: Scalar - `b`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \ceil **Description**: Round up to nearest integer. **Signature**: `\ceil a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \cos **Description**: Calculate cosine of a scalar value (in radians). **Signature**: `\cos a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \deg2rad **Description**: Convert degrees to radians. **Signature**: `\deg2rad a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \floor **Description**: Round down to nearest integer. **Signature**: `\floor a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \log10 **Description**: Calculate base-10 logarithm of a scalar value. **Signature**: `\log10 a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \max **Description**: Return maximum of two or more scalar values. **Signature**: `\max a b...` **Input types**: - `a`: Scalar - `b`: Scalar (variadic) **Output type**: Scalar **Auxiliary nodes**: None ### \min **Description**: Return minimum of two or more scalar values. **Signature**: `\min a b...` **Input types**: - `a`: Scalar - `b`: Scalar (variadic) **Output type**: Scalar **Auxiliary nodes**: None ### \mod **Description**: Calculate modulo (remainder) of first scalar divided by second. **Signature**: `\mod a b` **Input types**: - `a`: Scalar - `b`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \rad2deg **Description**: Convert radians to degrees. **Signature**: `\rad2deg a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \reciprocal **Description**: Calculate reciprocal (1/x) of a scalar value. **Signature**: `\reciprocal a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \relu **Description**: Apply ReLU activation function (max(0, x)). **Signature**: `\relu a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \round **Description**: Round to nearest integer. **Signature**: `\round a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \sigmoid **Description**: Apply sigmoid activation function (1 / (1 + e^(-x))). **Signature**: `\sigmoid a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \sign **Description**: Return sign of a scalar (-1, 0, or 1). **Signature**: `\sign a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \sin **Description**: Calculate sine of a scalar value (in radians). **Signature**: `\sin a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \tan **Description**: Calculate tangent of a scalar value (in radians). **Signature**: `\tan a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \tanh **Description**: Apply hyperbolic tangent activation function. **Signature**: `\tanh a` **Input types**: - `a`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \x-coord **Description**: Extract the x-coordinate from a point. **Signature**: `\x-coord point` **Input types**: - `point`: Point **Output type**: Scalar **Auxiliary nodes**: None ### \y-coord **Description**: Extract the y-coordinate from a point. **Signature**: `\y-coord point` **Input types**: - `point`: Point **Output type**: Scalar **Auxiliary nodes**: None ## Complex Functions ### \arg **Description**: Argument (phase angle) of a complex number, in radians **Signature**: `\arg z` **Input types**: - `z`: Complex **Output type**: Scalar **Auxiliary nodes**: None ### \complex **Description**: Construct a complex number from real and imaginary parts **Signature**: `\complex re im` **Input types**: - `re`: Scalar - `im`: Scalar **Output type**: Complex **Auxiliary nodes**: None ### \conj **Description**: Complex conjugate (re - im·i) **Signature**: `\conj z` **Input types**: - `z`: Complex **Output type**: Complex **Auxiliary nodes**: None ### \fft **Description**: Compute the fast Fourier transform of an array of scalars (any length). **Signature**: `\fft array` **Input types**: - `array`: Array **Output type**: Array **Auxiliary nodes**: el_0...el_n (Complex nodes) ### \ifft **Description**: Compute the inverse fast Fourier transform of an array (inverse of fft). **Signature**: `\ifft array` **Input types**: - `array`: Array **Output type**: Array **Auxiliary nodes**: el_0...el_n (Complex nodes) ### \imag **Description**: Extract the imaginary part of a complex number **Signature**: `\imag z` **Input types**: - `z`: Complex **Output type**: Scalar **Auxiliary nodes**: None ### \real **Description**: Extract the real part of a complex number **Signature**: `\real z` **Input types**: - `z`: Complex **Output type**: Scalar **Auxiliary nodes**: None ## Tensor Functions ### \arange **Description**: Create an array of Scalar values from start (inclusive) to end (exclusive) with a given step size **Signature**: `\arange start end step` **Input types**: - `start`: Scalar (default: 0) - `end`: Scalar (default: 5) - `step`: Scalar (default: 1) **Output type**: Array **Auxiliary nodes**: None ### \circular-arange **Description**: Create an array of n uniformly spaced points on a circle of radius r **Signature**: `\circular-arange n r` **Input types**: - `n`: Scalar (default: 10) - `r`: Scalar (default: 1) **Output type**: Array **Auxiliary nodes**: None ### \cumsum **Description**: Compute the cumulative sum of an array of scalars **Signature**: `\cumsum array` **Input types**: - `array`: Array **Output type**: Array **Auxiliary nodes**: None ### \linspace **Description**: Create an array of evenly spaced values. **Signature**: `\linspace start end n` **Input types**: - `start`: Scalar (default: 0) - `end`: Scalar (default: 1) - `n`: Scalar (default: 10) **Output type**: Array **Auxiliary nodes**: el_0...el_n (Scalar nodes) ### \ones **Description**: Create a 1-D array of n Scalar elements all equal to 1 **Signature**: `\ones n` **Input types**: - `n`: Scalar (default: 1) **Output type**: Array **Auxiliary nodes**: None ### \ones-like **Description**: Create an array of Scalar elements all equal to 1, matching the shape of the input array **Signature**: `\ones-like array` **Input types**: - `array`: Array **Output type**: Array **Auxiliary nodes**: None ### \reduce-max **Description**: Find the maximum element in an array. **Signature**: `\reduce-max array dim` **Input types**: - `array`: Array - `dim`: Scalar (default: -1) **Output type**: Any **Auxiliary nodes**: None ### \reduce-mean **Description**: Compute the mean of elements in an array of scalars, optionally along a dimension **Signature**: `\reduce-mean array dim` **Input types**: - `array`: Array - `dim`: Scalar (default: -1) **Output type**: Any **Auxiliary nodes**: None ### \reduce-min **Description**: Find the minimum element in an array. **Signature**: `\reduce-min array dim` **Input types**: - `array`: Array - `dim`: Scalar (default: -1) **Output type**: Any **Auxiliary nodes**: None ### \reduce-std **Description**: Compute the standard deviation of elements in an array of scalars, optionally along a dimension **Signature**: `\reduce-std array dim` **Input types**: - `array`: Array - `dim`: Scalar (default: -1) **Output type**: Any **Auxiliary nodes**: None ### \reduce-sum **Description**: Sum elements of an array. **Signature**: `\reduce-sum array dim` **Input types**: - `array`: Array - `dim`: Scalar (default: -1) **Output type**: Any **Auxiliary nodes**: None ### \reduce-var **Description**: Compute the variance of elements in an array of scalars, optionally along a dimension **Signature**: `\reduce-var array dim` **Input types**: - `array`: Array - `dim`: Scalar (default: -1) **Output type**: Any **Auxiliary nodes**: None ### \reshape **Description**: Reshape an array into a new N-dimensional shape **Signature**: `\reshape array dim...` **Input types**: - `array`: Array - `dim`: Scalar (variadic) **Output type**: Array **Auxiliary nodes**: None ### \softmax **Description**: Apply softmax function to an array of scalars **Signature**: `\softmax array` **Input types**: - `array`: Array **Output type**: Array **Auxiliary nodes**: None ### \zeros **Description**: Create a 1-D array of n Scalar elements all equal to 0 **Signature**: `\zeros n` **Input types**: - `n`: Scalar (default: 1) **Output type**: Array **Auxiliary nodes**: None ### \zeros-like **Description**: Create an array of Scalar elements all equal to 0, matching the shape of the input array **Signature**: `\zeros-like array` **Input types**: - `array`: Array **Output type**: Array **Auxiliary nodes**: None ## Arrays ### \array **Description**: Create an array of elements of the same type. **Signature**: `\array element1...` **Input types**: - `element1`: Any (variadic) **Output type**: Array **Auxiliary nodes**: None ### \get-array-element **Description**: Get an element from an array by index. **Signature**: `\get-array-element array index` **Input types**: - `array`: Array - `index`: Scalar **Output type**: Any **Auxiliary nodes**: None ## Transformations ### \animate **Description**: Animate a value over time. **Signature**: `\animate s animateTo` **Input types**: - `s`: Scalar - `animateTo`: Scalar **Output type**: Scalar **Auxiliary nodes**: None ### \rotate **Description**: Rotate a point or set of points around a center. **Signature**: `\rotate obj center angle` **Input types**: - `obj`: Any - `center`: Point - `angle`: Scalar **Output type**: Any **Auxiliary nodes**: None ### \translate **Description**: Translate a point or set of points. **Signature**: `\translate obj dx dy` **Input types**: - `obj`: Any - `dx`: Scalar - `dy`: Scalar **Output type**: Any **Auxiliary nodes**: None ### \translate-array **Description**: Translate an array of geometric objects by dx and dy (imperative) **Signature**: `\translate-array array dx dy` **Input types**: - `array`: Array - `dx`: Scalar - `dy`: Scalar **Output type**: Array **Auxiliary nodes**: None ## Autograd & Optimization ### \backprop **Description**: Run autodiff backpropagation from a target node. **Signature**: `\backprop id` **Input types**: - `id`: Any **Output type**: Dummy **Auxiliary nodes**: None ### \gradient-descent-step **Description**: Perform gradient descent on a parameter. **Signature**: `\gradient-descent-step id...` **Input types**: - `id`: Any (variadic) **Output type**: Dummy **Auxiliary nodes**: None ### \minimize **Description**: Minimize a scalar value using gradient descent for N iterations **Signature**: `\minimize value num-iterations` **Input types**: - `value`: Any - `num-iterations`: Any **Output type**: Dummy **Auxiliary nodes**: None ### \param **Description**: Register parameters for autodiff backpropagation. **Signature**: `\param id...` **Input types**: - `id`: Any (variadic) **Output type**: Dummy **Auxiliary nodes**: None ### \partial **Description**: Partial derivative ∂target/∂param of a scalar target w.r.t. a Scalar or Point variable, or element-wise over an Array of them (reactive) **Signature**: `\partial target param` **Input types**: - `target`: Scalar - `param`: Any **Output type**: Any **Auxiliary nodes**: None ### \reevaluate **Description**: Re-evaluate a node using current dependency values and snapshot the result into the store **Signature**: `\reevaluate id` **Input types**: - `id`: Any **Output type**: Dummy **Auxiliary nodes**: None ### \vector-field **Description**: Vector field from a scalar gradient (y: Scalar) or direct R²→R² map (y: Point). **Signature**: `\vector-field xId yId` **Input types**: - `xId`: Any - `yId`: Any **Output type**: VectorField **Auxiliary nodes**: arrows (Vector data) ### \zero-grad **Description**: Zero out gradients for all autodiff parameters. **Signature**: `\zero-grad ` **Input types**: **Output type**: Dummy **Auxiliary nodes**: None ## Boolean & Bitwise ### \and **Description**: Returns true if all inputs are true **Signature**: `\and values...` **Input types**: - `values`: Bool (variadic) **Output type**: Bool **Auxiliary nodes**: None ### \bin-to-dec-ones-complement **Description**: Interpret a binary string as a one's complement signed integer **Signature**: `\bin-to-dec-ones-complement value` **Input types**: - `value`: Text **Output type**: Scalar **Auxiliary nodes**: None ### \bin-to-dec-twos-complement **Description**: Interpret a binary string as a two's complement signed integer **Signature**: `\bin-to-dec-twos-complement value` **Input types**: - `value`: Text **Output type**: Scalar **Auxiliary nodes**: None ### \bin-to-dec-unsigned **Description**: Interpret a binary string as an unsigned integer **Signature**: `\bin-to-dec-unsigned value` **Input types**: - `value`: Text **Output type**: Scalar **Auxiliary nodes**: None ### \bool **Description**: Convert a value to a boolean based on its truthiness **Signature**: `\bool value` **Input types**: - `value`: Any **Output type**: Bool **Auxiliary nodes**: None ### \eq **Description**: Returns true if a == b **Signature**: `\eq a b` **Input types**: - `a`: Scalar - `b`: Scalar **Output type**: Bool **Auxiliary nodes**: None ### \filter **Description**: Filter an array by a boolean mask. The mask must broadcast with the array, with exactly one non-broadcast axis specifying which indices to keep. **Signature**: `\filter array mask` **Input types**: - `array`: Array - `mask`: Array **Output type**: Array **Auxiliary nodes**: None ### \fp-to-bin **Description**: Convert a scalar to its IEEE 754 floating-point binary string (16, 32 or 64 bits) **Signature**: `\fp-to-bin value nBits` **Input types**: - `value`: Scalar - `nBits`: Scalar (default: 32) **Output type**: Text **Auxiliary nodes**: None ### \ge **Description**: Returns true if a >= b **Signature**: `\ge a b` **Input types**: - `a`: Scalar - `b`: Scalar **Output type**: Bool **Auxiliary nodes**: None ### \gt **Description**: Returns true if a > b **Signature**: `\gt a b` **Input types**: - `a`: Scalar - `b`: Scalar **Output type**: Bool **Auxiliary nodes**: None ### \int-to-bin **Description**: Convert a scalar to its integer binary string (two's or one's complement) over a fixed bit width **Signature**: `\int-to-bin value nBits useTwosComplement` **Input types**: - `value`: Scalar - `nBits`: Scalar (default: 8) - `useTwosComplement`: Bool (default: T) **Output type**: Text **Auxiliary nodes**: None ### \le **Description**: Returns true if a <= b **Signature**: `\le a b` **Input types**: - `a`: Scalar - `b`: Scalar **Output type**: Bool **Auxiliary nodes**: None ### \lt **Description**: Returns true if a < b **Signature**: `\lt a b` **Input types**: - `a`: Scalar - `b`: Scalar **Output type**: Bool **Auxiliary nodes**: None ### \not **Description**: Returns the logical negation of a boolean value **Signature**: `\not value` **Input types**: - `value`: Bool **Output type**: Bool **Auxiliary nodes**: None ### \or **Description**: Returns true if any input is true **Signature**: `\or values...` **Input types**: - `values`: Bool (variadic) **Output type**: Bool **Auxiliary nodes**: None ### \uint-to-bin **Description**: Convert an unsigned non-negative integer to its binary string over a fixed bit width **Signature**: `\uint-to-bin value nBits` **Input types**: - `value`: Scalar - `nBits`: Scalar (default: 8) **Output type**: Text **Auxiliary nodes**: None ### \xor **Description**: Returns true if exactly one of the two inputs is true **Signature**: `\xor a b` **Input types**: - `a`: Bool - `b`: Bool **Output type**: Bool **Auxiliary nodes**: None ## Special Functions ### \clear **Description**: Clear all nodes from state and canvas. **Signature**: `\clear ` **Input types**: **Output type**: Dummy **Auxiliary nodes**: None ### \copy **Description**: Create an independent copy of a geometric node **Signature**: `\copy node` **Input types**: - `node`: Any **Output type**: Any **Auxiliary nodes**: None ### \help **Description**: Show properties of a node. **Signature**: `\help id` **Input types**: - `id`: Any **Output type**: Dummy **Auxiliary nodes**: None ### \hide **Description**: Hide a node from rendering. **Signature**: `\hide id...` **Input types**: - `id`: Any (variadic) **Output type**: Dummy **Auxiliary nodes**: None ### \highlight **Description**: Highlight an element. **Signature**: `\highlight id...` **Input types**: - `id`: Any (variadic) **Output type**: Dummy **Auxiliary nodes**: None ### \remove **Description**: Remove a node and all its dependencies. **Signature**: `\remove id...` **Input types**: - `id`: Any (variadic) **Output type**: Dummy **Auxiliary nodes**: None ### \set-fill **Description**: Set the fill color of a node. **Signature**: `\set-fill node fill` **Input types**: - `node`: Any - `fill`: Text **Output type**: Dummy **Auxiliary nodes**: None ### \set-stroke **Description**: Set the stroke color of a node. **Signature**: `\set-stroke node stroke` **Input types**: - `node`: Any - `stroke`: Text **Output type**: Dummy **Auxiliary nodes**: None ### \show **Description**: Show a previously hidden node. **Signature**: `\show id...` **Input types**: - `id`: Any (variadic) **Output type**: Dummy **Auxiliary nodes**: None ## ODEs ### \eval-ode **Description**: Point (t, y(t)) on an ODE trajectory at time t (interpolated) **Signature**: `\eval-ode trajectory t` **Input types**: - `trajectory`: Trajectory - `t`: Scalar **Output type**: Point **Auxiliary nodes**: None ### \flow **Description**: Integrate dp/dt = F(p) through a 2D vector field and draw the trajectory **Signature**: `\flow point out p0 t1 steps` **Input types**: - `point`: Point - `out`: Any - `p0`: Point - `t1`: Scalar - `steps`: Scalar (default: 200) **Output type**: Trajectory **Auxiliary nodes**: None ### \simulate-sde **Description**: Simulate dX = a(t,X) dt + b(t,X) dW (Euler–Maruyama) and draw the sample path **Signature**: `\simulate-sde t x drift diffusion x0 t1 steps seed` **Input types**: - `t`: Scalar - `x`: Scalar - `drift`: Any - `diffusion`: Any - `x0`: Scalar - `t1`: Scalar - `steps`: Scalar (default: 200) - `seed`: Scalar (default: -1) **Output type**: Trajectory **Auxiliary nodes**: None ### \solve-ode **Description**: Integrate dy/dt = f(t, y) from t=0 and draw the trajectory (t, y(t)) **Signature**: `\solve-ode t y dydt y0 t1 steps` **Input types**: - `t`: Scalar - `y`: Scalar - `dydt`: Any - `y0`: Scalar - `t1`: Scalar - `steps`: Scalar (default: 200) **Output type**: Trajectory **Auxiliary nodes**: None ## Annotations ### \annotate-angle-mark **Description**: Arc marking the angle at the shared vertex of two lines, with optional label **Signature**: `\annotate-angle-mark line1 line2 label` **Input types**: - `line1`: Line - `line2`: Line - `label`: Text (default: ) **Output type**: AngleMark **Auxiliary nodes**: None ### \annotate-arrow **Description**: Straight arrow from p1 to p2 with optional label. padding (0–0.5) shrinks both ends by that fraction of the total length. **Signature**: `\annotate-arrow p1 p2 padding label` **Input types**: - `p1`: Point - `p2`: Point - `padding`: Scalar (default: 0) - `label`: Text (default: ) **Output type**: Arrow **Auxiliary nodes**: None ### \annotate-curly-bracket **Description**: Curly bracket between two points with optional label (opens to right of p1→p2) **Signature**: `\annotate-curly-bracket p1 p2 label` **Input types**: - `p1`: Point - `p2`: Point - `label`: Text (default: ) **Output type**: CurlyBracket **Auxiliary nodes**: None ### \annotate-curved-arrow **Description**: Curved arrow from p1 to p2 bending toward a control point. padding (0–0.5) trims both ends by that fraction of the curve. **Signature**: `\annotate-curved-arrow p1 p2 control padding label` **Input types**: - `p1`: Point - `p2`: Point - `control`: Point - `padding`: Scalar (default: 0) - `label`: Text (default: ) **Output type**: CurvedArrow **Auxiliary nodes**: None ### \annotate-dim-line **Description**: Dimension line between two points with extension lines and auto-distance label **Signature**: `\annotate-dim-line p1 p2 label` **Input types**: - `p1`: Point - `p2`: Point - `label`: Text (default: ) **Output type**: DimensionLine **Auxiliary nodes**: None ### \annotate-leader-line **Description**: Leader line with arrowhead at p1, elbow, and label at p2 **Signature**: `\annotate-leader-line p1 p2 label` **Input types**: - `p1`: Point - `p2`: Point - `label`: Text **Output type**: LeaderLine **Auxiliary nodes**: None ### \annotate-pin **Description**: Pin marker at a point with optional label **Signature**: `\annotate-pin position label` **Input types**: - `position`: Point - `label`: Text (default: ) **Output type**: Pin **Auxiliary nodes**: None ### \annotate-text-box **Description**: Text label centered at a point, with optional wrapping box. Width/height semantics: w=0, h=0 → no bounded box, single-line label (default). w>0, h>0 → fixed box. w>0, h=-1 → fixed width, height grows to fit wrapped text. w=-1, h>0 → fixed height, width sized to fit text in that many lines. w=-1, h=-1 → both auto, each capped at 5 canvas units. **Signature**: `\annotate-text-box text x y fontSize width height` **Input types**: - `text`: Text - `x`: Scalar (default: 0) - `y`: Scalar (default: 0) - `fontSize`: Scalar (default: 14) - `width`: Scalar (default: 0) - `height`: Scalar (default: 0) **Output type**: TextBox **Auxiliary nodes**: None ## Overloaded Functions ### \abs **Description**: Absolute value / modulus (scalar or complex) **Signature**: `\abs a` **Works on**: Scalar, Complex **Input types**: - `a`: Any **Output type**: Any **Auxiliary nodes**: None ### \add **Description**: Add two or more values (scalar or complex) **Signature**: `\add a b...` **Works on**: Scalar, Complex **Input types**: - `a`: Any - `b`: Any (variadic) **Output type**: Any **Auxiliary nodes**: None ### \div **Description**: Divide the first value by the second (scalar or complex) **Signature**: `\div a b` **Works on**: Scalar, Complex **Input types**: - `a`: Any - `b`: Any **Output type**: Any **Auxiliary nodes**: None ### \exp **Description**: Exponential e^a (scalar or complex) **Signature**: `\exp a` **Works on**: Scalar, Complex **Input types**: - `a`: Any **Output type**: Any **Auxiliary nodes**: None ### \log **Description**: Natural logarithm ln(a) (scalar or complex) **Signature**: `\log a` **Works on**: Scalar, Complex **Input types**: - `a`: Any **Output type**: Any **Auxiliary nodes**: None ### \mul **Description**: Multiply two or more values (scalar or complex) **Signature**: `\mul a b...` **Works on**: Scalar, Complex **Input types**: - `a`: Any - `b`: Any (variadic) **Output type**: Any **Auxiliary nodes**: None ### \neg **Description**: Negate a value (scalar or complex) **Signature**: `\neg a` **Works on**: Scalar, Complex **Input types**: - `a`: Any **Output type**: Any **Auxiliary nodes**: None ### \pow **Description**: Raise the first value to the power of the second (scalar or complex) **Signature**: `\pow a b` **Works on**: Scalar, Complex **Input types**: - `a`: Any - `b`: Any **Output type**: Any **Auxiliary nodes**: None ### \sqrt **Description**: Square root (scalar or complex) **Signature**: `\sqrt a` **Works on**: Scalar, Complex **Input types**: - `a`: Any **Output type**: Any **Auxiliary nodes**: None ### \sub **Description**: Subtract the second value from the first (scalar or complex) **Signature**: `\sub a b` **Works on**: Scalar, Complex **Input types**: - `a`: Any - `b`: Any **Output type**: Any **Auxiliary nodes**: None --- # Macro Examples Macros are provided as elements in a JSON array using `.json` files. Each item is a macro object with a `macro` field (name) and a `commands` field (array of command strings). Commands execute sequentially; variables defined in earlier commands are available in later ones. Key rules: - No nesting: `y = \function (\another-function x1) x2` is invalid. Use two commands. - Every command is either an assignment `varName = \function args...` or a side-effect `\function args...`. - Broadcasting: functions that accept a scalar type will broadcast over an array of that type. - `p0` is a built-in origin point at (0, 0). ```json [ { "macro": "macro-rolling-coin", "commands": [ "r0 = \\scalar 1", "r1 = \\scalar 1", "deg = \\scalar 0", "rad = \\deg2rad deg", "r0-by-r1 = \\div r0 r1", "rotation-rad = \\mul r0-by-r1 rad", "rotation-rad-plus-rad = \\add rotation-rad rad", "r-sum = \\add r0 r1", "cos-rad = \\cos rad", "sin-rad = \\sin rad", "cos-rotation = \\cos rotation-rad-plus-rad", "sin-rotation = \\sin rotation-rad-plus-rad", "cx = \\mul r-sum cos-rad", "cy = \\mul r-sum sin-rad", "delta-x = \\mul r1 cos-rotation", "delta-y = \\mul r1 sin-rotation", "tx = \\sub cx delta-x", "ty = \\sub cy delta-y", "c0 = \\circle p0 r0", "c = \\point cx cy", "c1 = \\circle c r1", "p = \\point tx ty", "\\line c p", "\\trail p", "animation-speed = \\scalar 0.05", "\\animate deg 360" ] }, { "macro": "macro-parabola-from-lines", "commands": [ "n = \\scalar 10", "xs = \\linspace -5 5 n", "ys = \\linspace 4 -2 n", "px = \\point xs -2", "py = \\point -5 ys", "\\line px py", "n = \\scalar 15", "n = \\scalar 5", "n = \\scalar 15", "n = \\scalar 10" ] }, { "macro": "macro-sliding-ladder", "commands": [ "len = \\scalar 5", "deg = \\scalar 90", "rad = \\deg2rad deg", "cos = \\cos rad", "sin = \\sin rad", "len-times-cos = \\mul len cos", "len-times-sin = \\mul len sin", "px = \\point len-times-cos 0", "py = \\point 0 len-times-sin", "ladder = \\line px py", "m0 = \\mid-point px py", "m1 = \\mid-point m0 px", "m2 = \\mid-point m0 py", "\\trail m0", "\\trail m1", "\\trail m2", "\\animate deg 0" ] }, { "macro": "macro-tusi-couple", "commands": [ "r0 = \\scalar 4", "r1 = \\scalar 2", "r-diff = \\sub r0 r1", "r-diff-by-r1 = \\div r-diff r1", "deg = \\scalar 0", "rad = \\deg2rad deg", "scaled-rad = \\mul r-diff-by-r1 rad", "neg-scaled-rad = \\neg scaled-rad", "angles = \\array rad neg-scaled-rad", "cosines = \\cos angles", "sines = \\sin angles", "coeffs = \\array r-diff r1", "tmp-x = \\mul coeffs cosines", "tmp-y = \\mul coeffs sines", "x = \\reduce-sum tmp-x", "y = \\reduce-sum tmp-y", "\\circle p0 r0", "cos-rad = \\get-array-element cosines 0", "sin-rad = \\get-array-element sines 0", "cx = \\mul r-diff cos-rad", "cy = \\mul r-diff sin-rad", "c = \\point cx cy", "\\circle c r1", "p = \\point x y", "\\trail p", "\\animate deg 360" ] }, { "macro": "macro-power-of-a-point", "commands": [ "c0 = \\circle p0 2", "p = \\point 3 -2", "q = \\point -3 0", "pq = \\line p q", "\\intersection-line-circle pq c0", "\\highlight p1 p2", "p-p1 = \\distance p p1", "p-p2 = \\distance p p2", "dist-product = \\mul p-p1 p-p2", "\\point dist-product 0", "\\translate q 0 2", "\\translate q -2 -1", "q = \\point 0 0", "q = \\point -4 0", "\\rotate q p -30" ] }, { "macro": "macro-lissajous", "commands": [ "a = \\scalar 3", "b = \\scalar 2", "delta-deg = \\scalar 90", "delta = \\deg2rad delta-deg", "t-a-deg = \\scalar 0", "t-b-deg = \\scalar 0", "t-a = \\deg2rad t-a-deg", "t-b = \\deg2rad t-b-deg", "a-times-t = \\mul a t-a", "b-times-t = \\mul b t-b", "b-times-t-plus-delta = \\add b-times-t delta", "x = \\sin a-times-t", "y = \\sin b-times-t-plus-delta", "x-scaled = \\mul 3 x", "y-scaled = \\mul 3 y", "p = \\point x-scaled y-scaled", "p-left = \\point -5 y-scaled", "p-top = \\point x-scaled 5", "\\line p-left p", "\\line p-top p", "\\trail p", "animation-speed = \\scalar 0.05", "t-array = \\array t-a-deg t-b-deg", "\\animate t-array 720" ] }, { "macro": "macro-convex-hull-points-on-curve", "commands": [ "a = \\scalar 0.3", "pol = \\polynomial -3 0 a", "xs = \\array 1 -2 5.2 -4.5", "ys = \\evaluate-polynomial pol xs", "\\point xs ys", "\\convex-hull p1 p2 p3 p4", "a = \\scalar 0.2" ] } ] ``` --- # Interactive Article Examples Articles are extended markdown where inline text triggers Geomatic commands via `{description}(command)` syntax. Commands with no visual output go under `####` headings (collapsible). LaTeX math is supported with `$...$` and `$$...$$`. Sliders use `::slider[values, target]` or `::circular-slider[values, target]`. Key rules: - Each `{description}(command)` pair is one command — no nesting. - `\set-stroke node color` and `\set-fill node color` change colors; `color` must be a `\text` node. - `\highlight` and `\hide` accept multiple space-separated node names. - Commands under `####` are collapsible and typically have no visual output. - Reactive UI components: `::slider[arrayOfValues, targetVariable]` and `::circular-slider[arrayOfValues, targetVariable]`. --- ### Example: Peaucellier–Lipkin Linkage ``` The **Peaucellier–Lipkin linkage** ([Wikipedia](https://en.wikipedia.org/wiki/Peaucellier–Lipkin_linkage)) was the first planar linkage to produce exact straight line motion from a rotary motion of a hand. To demonstrate how it works, we declaratively perform the following steps: #### Setting up scalar values Running these commands does not show anything on the canvas. - {radius of rotation of the hand}(r1 = \scalar 0.9): `r1 = \scalar 0.9` - {angle of rotation of the hand}(theta = \scalar 0): `theta = \scalar 0` - {convert theta to radians}(thetaRad = \deg2rad theta): `thetaRad = \deg2rad theta` - {distance from origin to fixed point}(r2 = \scalar 2.6): `r2 = \scalar 2.6` - {origin point}(p0 = \point 0 0): `p0 = \point 0 0` - {point at distance r1 from origin}(p1 = \point r1 0): `p1 = \point r1 0` - {cosine of theta}(cosTheta = \cos thetaRad): `cosTheta = \cos thetaRad` - {sine of theta}(sinTheta = \sin thetaRad): `sinTheta = \sin thetaRad` - {x-component of rotation}(r1cosTheta = \multiply r1 cosTheta): `r1cosTheta = \multiply r1 cosTheta` - {y-component of rotation}(r1sinTheta = \multiply r1 sinTheta): `r1sinTheta = \multiply r1 sinTheta` - {x-coordinate of rotating point}(p2x = \add r1 r1cosTheta): `p2x = \add r1 r1cosTheta` - {y-coordinate of rotating point}(p2y = \add 0 r1sinTheta): `p2y = \add 0 r1sinTheta` --- ### Setting up the visible apparatus Now we create the points and lines: - {the rotating point}(p2 = \point p2x p2y): `p2 = \point p2x p2y` - {line from origin to p1}(line01 = \line p0 p1): `line01 = \line p0 p1` This is the rotating hand: - {line from p1 to rotating point}(line12 = \line p1 p2): `line12 = \line p1 p2` You can also: - {highlight the hand}(\highlight line12) to avoid any confusion about which line it is. - {define a new color}(color = \text fff) and {set it to be the line's color}(\set-stroke line12 color) - {radius for circle around p2}(r3 = \multiply 1.2 r1): `r3 = \multiply 1.2 r1` The fun part begins here: we create a circle of radius `r3` centered at `p2` and another circle of radius `r2` centered at the origin. - {circle centered at p2}(c0 = \circle p2 r3): `c0 = \circle p2 r3` - {circle centered at origin}(c1 = \circle p0 r2): `c1 = \circle p0 r2` ... and draw a line joining their points of intersection: - {get intersection points}(intersection-points = \intersection-circle-circle c0 c1): `intersection-points = \intersection-circle-circle c0 c1` - Get {the first point}(p-inter0 = \get-array-element intersection-points 0) and {the second point}(p-inter1 = \get-array-element intersection-points 1) - Join them {with a line}(line0 = \line p-inter0 p-inter1) Finally the {point p2}(\highlight p2) is reflected across the line to create the final point: - {reflected point tracing straight line}(pFinal = \reflect-point p2 line0): `pFinal = \reflect-point p2 line0` Now as you rotate {this point}(\highlight p2), you'll notice that {the reflected point}(\highlight pFinal) traces a straight line! If you update the value of `theta`, by {setting it to 45 degrees}(theta = \scalar 45), the canvas reacts to it immediately. You can also {animate it to -45 degrees}(\animate theta -45). Notice how the rotation of the hand is translated into a linear (vertical) motion of {this point}(\highlight pFinal). --- #### Trying out different angles - {Create an array of angle values}(thetas = \linspace 0 360 24) ::circular-slider[thetas, theta] --- Run the `\peaucellier-lipkin-linkage` command in the [geomatic](/geomatic) editor and modify the variables to play with it. #### You can do things like: - removing unwanted shapes. Eg you can {hide the circles}(\hide c0 c1). - explore the geometry a bit more. Eg the fact that {these points}(\highlight p0 p2 pFinal) are always collinear. - you can {join them with a line}(\line p0 pFinal) ``` --- ### Example: Fun with Ellipses ``` Before we start, {clear the canvas}(\clear) first. ### 1. Setting up Start with the focal definition of ellipse. The focal points are: - {$a = (-2,0)$}(a = \point -2 0) - {$b = (2,0)$}(b = \point 2 0) - Let {$p = (1,1)$}(p = \point 1 1) #### 1.1 Constructing the string Join $p$ with the focal points: - {join $a$ and $p$}(\line a p) - {join $p$ and $b$}(\line p b) #### 1.2 Calculating the distances - {Distance $pa$}(pa = \distance p a) - {Distance $pb$}(pb = \distance p b) - {Total length $k = pa + pb$}(k = \add pa pb) Now comes the fun part. Tweak the values $pa$ and $pb$ so that their sum remains the same. - {$delta = 0$}(delta = \scalar 0) - {$ra = pa + delta$}(ra = \add pa delta) - {$rb = pb - delta$}(rb = \subtract pb delta) Draw circles with focal points as centers and $ra, rb$ as radii: - {circle $ca$}(ca = \circle a ra) - {circle $cb$}(cb = \circle b rb) Get the points of intersection of the two circles: - {get points of intersection $poi$}(poi = \intersection-circle-circle ca cb) and {highlight them}(\highlight poi) Now let's change delta: - {make $delta$ to be an array}(delta = \linspace -2.5 2.5 15) - {Hide the circles around $a$ to view the points clearly}(\hide ca) - {Hide the circles around $b$ to view the points clearly}(\hide cb) Note that these points of intersections lie on the ellipse which can be drawn like so: - {draw ellipse from focal points and $k$}(\ellipse-from-foci a b k) Let's reset $delta$: - {$delta = 0$}(delta = \scalar 0) - {hide circle $ca$}(\hide ca) - {hide circle $cb$}(\hide cb) --- ### 2. Some vector calculus We can calculate the gradient of $k$ wrt the point $p$ using these steps: - {register $p$ as a param}(\param p) - {backpropagate the scalar output $k$}(\backprop k) Now the gradient of $p$ appears as an arrow at the point. Note that it is perpendicular to the ellipse. ### 2.1 Vector field One can in fact calculate the gradient of $k$ wrt each point on the grid. The `\vector-field input output` command shows the gradients at each integer grid: - {show vector field}(\vector-field p k) --- ### 3. Reflection property {Remove the vector field}(\remove vf0) and focus on the ellipse again. - {Draw a circle centered at b with radius $k$}(c-big = \circle b k) - {Calculate the intersection of the line $p b$ with the circle}(\intersection-line-circle line1 c-big) If you are wondering how to figure out the variable names like `line1`, you can simply hover the mouse over a line or a circle to see its name. One of the points has name `p29`. Connect it to `p` with a line: - {line from p to p29}(l = \line p p29) Consider the two lines - {$(p-a)$ and $(p-p29)$}(lines = \array line0 l). It can be shown easily that {these two lines}(\highlight lines) have the same length (both of them added with $pb$ sum to $k$). The angle bisector of $\angle a-p-p29$ then reflects the line $p-p29$ which is the same as $p-b$ to line $p-a$: - {bisect $\angle a-p-p29$}(bisector = \bisect-angle line0 l) - {highlight the bisector}(\highlight bisector). This bisector reflects the {line $a-p$}(\highlight line0) to {line $p-b$}(\highlight line1). Note that it is perpendicular to the gradient at point $p$ and is tangent to the ellipse at the same point. #### 3.1 Proof of reflection property First reduce the clutter by hiding unwanted figures: - {hide the circle, origin and ellipse}(\hide c-big p0 ell0 p28 p26 p25) - {zero out the gradients}(\zero-grad) The proof is based on triangle property. Pick any point on the bisector. For convenience we pick the mid-point. - {mid-point $m$ of bisector}(m = \mid-point bisector) - {Create a triangle $m-p29-b$}(tr = \triangle m p29 b) - {Hide the triangle to show the lines}(\hide tr) By triangle inequality, we have the sum of {these two sides}(\highlight line2 line4) to always be greater than or equal to {this side, which equals $k$}(\highlight line3). Now let's draw {line from $a$ to $m$}(\line m a). Note that {these two sides are equal}(\highlight line5 line2). Thus the sum of {these two lines}(\highlight line4 line5) is greater than $k$. Hence the point $m$ must lie outside the ellipse. > Every point on the bisector is outside the ellipse except point $p$ which touches the ellipse. Hence the bisector is tangent to the ellipse. - {Show the ellipse again}(\show ell0) ``` --- ### Example: Polynomial Coefficients and Broadcasting ``` Here is a fun way to use broadcasting to get a hang of how each coefficient of a polynomial equation affects the behavior of its curve. We will use the following polynomial for this demo: $$y(x) = a_0 + a_1x + a_2x^2$$ #### Define coefficients first: - {a0 = 0}(a0 = \scalar 0) - {a1 = 1}(a1 = \scalar 1) - {a2 = 0.4}(a2 = \scalar 0.4) ### Define the polynomial - {Define $y(x)$}(y = \polynomial a0 a1 a2) --- ### Change $a_0$ This can be done in two ways: Set the {animation speed}(animation-speed = \scalar 0.005) - {animate $a_0$ to 3}(\animate a0 3) - {animate $a_0$ back to 0}(\animate a0 0) Another way is to visualize the plot for multiple values of $a_0$: - {define $a_0$ as an array}(a0 = \linspace -2 2 5) Thus, $a_0$ simply translates the curve vertically. You can also use a slider to change the values: - {Create an array of slider values}(as = \linspace -2 2 10) - {Reset $a_0 = -2$}(a0 = \scalar -2) ::slider[as,a0] --- ### Change `a1` Make sure to reset {$a_0 = 0$}(a0 = \scalar 0). - {Set $a_1$ to an array}(a1 = \linspace -3 3 10) You can also just animate it. - {Reset $a_1 = 1$}(a1 = \scalar 1) - {Animate $a_1$ to 3}(\animate a1 3) - {Animate $a_1$ to -3}(\animate a1 -3) Let's use a slider again: - {Create an array of slider values}(as = \linspace -2 2 10) - {Reset $a_1 = -2$}(a1 = \scalar -2) ::slider[as,a1] --- ### Change $a_2$ Lastly, you can try making the same changes to $a_2$: - {Reset $a_1 = 1$}(a1 = \scalar 1) - {Animate $a_2$ to 2}(\animate a2 2) - {Animate it to 0}(\animate a2 0) Now try broadcasting: - {Set $a_2$ to an array}(a2 = \linspace -2 2 10) Let's use a slider: - {Create an array of slider values}(as = \linspace -2 2 10) - {Reset $a_2 = -2$}(a2 = \scalar -2) ::slider[as,a2] ``` --- # Generating Commands from a Task This section teaches how to turn a natural-language task (e.g. *"visualize a cardioid"*) into a valid **list of Geomatic commands**. If you are asked to "generate commands" or "write a macro", produce a list where each element is a single command string, in execution order. ## The golden rules of syntax Every command has exactly one of two forms: ``` name = \function arg1 arg2 ... # assignment: stores the result as `name` \function arg1 arg2 ... # imperative: acts on the canvas / existing nodes ``` These rules are absolute — breaking any of them produces an invalid program: 1. **One command per line.** A command never spans multiple lines. 2. **No nested calls.** You cannot write `y = \add 1 (\mul 2 3)`. Compute the inner value first in its own command, then reference it by name: ``` tmp = \mul 2 3 y = \add 1 tmp ``` 3. **Arguments are identifiers, numbers, or property accesses.** Each argument is the name of an existing node, a number, or a dot-property access (rule 6). A **number** is an optional minus sign, then digits, then an optional decimal part: `2`, `-0.5`, `360` are valid; `+2`, `.5`, `1e3`, `1_000` are not. 4. **Identifiers must start with a letter, then contain only letters, digits, and hyphens** — e.g. `r1`, `cos-rad`, `t-init`, `point`. **No underscores, no spaces, no leading digit or hyphen, no other characters.** This one rule governs *every* name in a command: the assignment name, the command `\keyword`, and each node reference. (Assignment names must also be unique-per-role.) 5. **`\text` is the only command that takes a string.** It accepts a double-quoted string that may contain spaces and special characters, and supports `${...}` interpolation that embeds the live value of a node — e.g. `label = \text "Length is ${d}"` updates as `d` changes. Every other command accepts only identifiers, numbers, and property accesses — never quotes or `${...}`. 6. **Dot-property access** lets you read a field of a node without a helper command: `p.x`, `p.y`, `circle.center`, `circle.center.x`, `line.slope`. 7. **Reassignment is allowed and reactive.** Writing `n = \scalar 20` after `n` already exists updates `n` and everything downstream. This is how you animate or step through values. > **The set of commands is closed.** Use only the `\keyword`s defined in this document (the Function Reference below). Do not invent, guess, or substitute "similar" commands — if a capability is not listed, it does not exist unless the user explicitly provides it. ### Built-in nodes (always available) - **`p0`** — the origin point `(0, 0)`. Used as the default center for circles, polygons, etc. You don't need to create it (though `p0 = \point 0 0` is fine). - **`animation-speed`** — a scalar (default `0.05`) controlling how fast `\animate` runs. Set it with `animation-speed = \scalar 0.02`. - **`unit`** — a scalar (default `50`) controlling canvas scale; see the Design Principles section below. ## Naming nodes and auto-generated ids **Best practice: give every value you can an explicit name** (`name = \fn ...`). Named nodes are readable and unambiguous to reference later — prefer naming over relying on auto-generated ids. Some nodes still cannot be named, and they fall back to an auto-generated id: - the output of an **imperative command** with no `name =` (e.g. `\circle points dists`); - **auxiliary nodes** a command spawns as a side effect — a polygon's vertices and edges, an intersection's points, a tangent's endpoints. The Function Reference lists these under **Auxiliary nodes**. An auto id is the node's type abbreviation plus a counter that increments per type as nodes are created: | Type | id | Type | id | Type | id | |---|---|---|---|---|---| | Point | `p{n}` | Circle | `circ{n}` | Polygon | `poly{n}` | | Line | `line{n}` | Ellipse | `ell{n}` | RegularPolygon | `repo{n}` | | Scalar | `num{n}` | Arc | `arc{n}` | Polynomial | `polynomial{n}` | | Array | `arr{n}` | Triangle | `tr{n}` | Trajectory | `ode{n}` | Counters start at 0 (`line0`, `circ0`, …). Points are the exception: `p0` is the reserved origin, so auto-generated points begin at `p1`. Example: a bare `\point xs ys` over a 4-element array creates `p1 p2 p3 p4`, which `\convex-hull p1 p2 p3 p4` can then reference. (In the editor you can hover any shape to read its id.) ## Broadcasting (the key to elegant drawings) Almost every function broadcasts. If a function expects a single value of type `T` and you pass it an **array of `T`**, it runs element-wise and returns an array. If several arguments are arrays they must be the **same length** and are **zipped** together. ``` xs = \linspace -5 5 11 # array of 11 scalars ys = \mul 2 xs # array of 11 scalars (2 * each) pts = \point xs ys # array of 11 points \circle pts 1 # draws 11 circles, one per point ``` This is the idiomatic way to draw families of shapes (envelopes, vector fields, spirographs) without loops. The cardioid below is built entirely on broadcasting. ## Reactivity: animation To vary a value and watch the canvas respond, **animate** smoothly drives a scalar to a target: ``` \animate theta 360 ``` Reassigning a scalar (`n = \scalar 20`) also flows through every dependent node and redraws instantly. ## Command reference For the full list of commands with signatures, input/output types, and defaults, use the **Function Reference** section near the top of this file. A few reminders relevant to generation: - Trig functions (`\sin`, `\cos`, `\tan`, …) take **radians** — convert degrees with `\deg2rad`. - `\add`, `\mul`, `\min`, `\max` are variadic (two or more args); `\sub`, `\div`, `\pow`, `\mod` are binary. - Drawing commands (`\point`, `\line`, `\circle`, `\polygon`, …) are what put marks on the canvas; pure arithmetic is invisible until you draw with its result. ## Worked example — *"visualize a cardioid"* A cardioid is the envelope of a family of circles whose centers lie on a fixed circle and whose radii equal the distance from each center to a fixed point. The construction is a textbook broadcasting problem: ``` n = \scalar 6 n-minus-one = \sub n 1 nums = \linspace 0 n-minus-one n fractions = \div nums n degrees = \mul 360 fractions radians = \deg2rad degrees r = \scalar 2 cos = \cos radians sin = \sin radians rcos = \mul r cos rsin = \mul r sin points = \point rcos rsin dists = \distance points p0 \circle points dists ``` Walk-through of the reasoning: 1. **Pick how many circles.** `n` is a scalar so it can later be swept. 2. **Spread `n` angles around the circle.** `\linspace 0 n-1 n` → fractions → degrees → radians. (Each is its own line: no nesting.) 3. **Place the centers** on a circle of radius `r` using `\point rcos rsin`. Because `rcos`/`rsin` are arrays, `points` is an array of `n` points — broadcasting at work. 4. **Radius of each circle** = distance from its center to the fixed point (here the origin `p0`). 5. **Draw them all at once:** `\circle points dists` broadcasts over both arrays. That's a complete, valid command list. Reassigning `n` (e.g. `n = \scalar 20`) flows through every dependent node and redraws the whole family — making the envelope sharper as the count grows. ## Checklist before returning commands - Every line is `name = \fn ...` or `\fn ...` — nothing else. - No nested calls; every intermediate value has its own line. - Every `\keyword` used appears in this document — no invented or "similar" commands. - Identifiers start with a letter and use only letters/digits/hyphens; no underscores or spaces. - Only `\text` uses quotes and `${...}`; every other argument is an identifier, a number, or a `node.field` access. - Prefer an explicit `name =` for every value; only auxiliary/imperative outputs rely on auto-ids (`p1`, `circ0`, `line0`, …). - Each referenced node is defined earlier (or is built-in `p0` / `animation-speed` / `unit`). - Trig is in radians — convert degrees with `\deg2rad`. - Arrays passed to a function broadcast; zipped arrays share a length. - Drawing commands actually put marks on the canvas; pure arithmetic is invisible until you draw with it. --- # Linear Algebra Extension Commands (`\la-*`) The `\la-*` commands are **hosted extensions**: they are defined in the `geomatic-math-extensions` repo (`src/linear-algebra.ts`) and loaded at runtime from Supabase, so they do NOT appear in the auto-generated function reference (the Function Reference above). All the usual grammar rules apply (see "Generating Commands from a Task" above): one command per line, no nested calls, identifiers start with a letter and use hyphens (never underscores), only `\text` takes a quoted string. --- ## Signatures - `\la-vec2d x y [offsetX=0 offsetY=0]` → **Arrow** (with head) from `(offsetX, offsetY)` to `(offsetX+x, offsetY+y)`. Takes 2 or 4 **Scalars** (NOT a Point — passing a Point is a silent bug). The 4-arg form draws a tail-anchored vector, e.g. a normal `q` attached at `p`'s tip: `\la-vec2d qx qy px py`. All scalar args broadcast over Arrays, so array-valued `x`/`y` draw a whole family of arrows in one command. - `\la-vec s1 s2 ...` (variadic Scalars) → **Array** (a column vector). - `\la-mat-from-points p1 p2 ...` (variadic Points) → **Array**; each point is a **column** of the matrix. - `\la-mat-from-point-array pointsArray` → same, but points arrive as one Array. - `\la-mat-to-points matrix` → Array of Points (columns → points). - `\la-matmul a b` → 2D **Array** (matrix product). 1D operands are promoted NumPy-style (left prefers row, right prefers column); throws on incompatible shapes. - `\la-dot a b` → **Scalar** Σaᵢbᵢ (equal-length Arrays of scalars). - `\la-project v1 v2` → **Arrow** from origin = projection of v1 onto v2 ((v1·v2 / v2·v2)·v2). Inputs are length-2 **Arrays** (NOT the x,y-scalar form used by `\la-vec2d`). - `\la-vec2d-radian x y` → **Scalar** angle of (x, y) via atan2, range (−π, π]. Combine with `\rad2deg` to derive an `\animate` target instead of hardcoding angles. - `\la-weighted-sum matrix weights` → **Arrow**; draws the tip-to-tail chain of scaled columns plus the resultant sum arrow. `weights` is an Array (`\ones n` or `\array s1 s2 s3` of individual `\scalar` nodes so each stays tweakable). - `\la-grid-transform matrix` → grid-warp visual of the linear map. - `\la-circle-transform matrix [n=60]` → rainbow **fan from the origin** to M·(unit-circle points). The matrix is read row-major from `elements[0..3]`, so a flat `\array a b c d` works with no reshape. - `\la-circle-transform2 matrix [n=60]` → same sampling, but each line joins **input point → output point** (displacement view). - `\la-array-to-textboxes array x y [cellWidth=1 cellHeight=1 fontSize=14]` → renders a 1D/2D Array as a grid of TextBoxes: top-left cell at (x, y), columns → +x, rows → −y, values rounded to 2 decimals. Feed it a reshaped 2×2 node for a live matrix readout that updates as entries animate. --- ## Hard-won footguns - **Unit-circle sampling is hardcoded.** Both `\la-circle-transform` variants sample inputs on the radius-1 circle (`cos θ`, `sin θ` — there is no radius argument). Any reference `\circle p0 r` or swept input vector drawn alongside MUST use `r = 1`, or it will not coincide with the fan's input points. Get on-screen size from `unit` (e.g. `unit = \scalar 70`) instead of a bigger radius. Eigenvector lines and label anchors then live in unit-circle-scale coords (~±1.7 span for typical 2×2 examples). - **`\hide` on a fan is a PARTIAL hide**: the spoke lines hide but the output points remain visible as a ghost ring of dots. Do not build reveal sequences that rely on fully hiding a fan; prefer creating each stage progressively at the click that describes it (see Design Principles below). - **`\la-vec2d` takes scalars, not a Point.** To draw a row vector from matrix entries: `\la-vec2d col1x col2x`. - **`\annotate-angle-mark` needs Line inputs.** To mark the angle of an offset `\la-vec2d` arrow, keep a parallel hidden `\line tip endpt`. - Fewer spokes read better when vectors sit on top: drop `n` from 60/48 to ~20–24 so the fan reads as an ellipse, not a wall of lines. --- ## Proven recipes (verified in LAFP chapter ports) - **Reactive 2×2 matrix**: entry scalars (e.g. `a = \mul s1 r1x`) → `mflat = \array a b c d` → `m = \reshape mflat 2 2`. Feed `m` (or `mflat`) to `\la-circle-transform*`, `m` to `\la-array-to-textboxes`. Animating any entry scalar updates everything. - **Transpose for free**: `atflat = \array a c b d` — re-list the same entry node refs in transposed order. - **Row norm**: `\mul a a` + `\mul b b` → `\add` → `\sqrt`; negate with `\neg` for symmetric bounds. - **Rotate a base vector by an animated angle t**: `ct = \cos t`, `st = \sin t`, then per-component `\mul` + `\add`/`\sub` (constants as `\scalar` nodes). - **Many vectors at once (broadcasting beats animating one)**: build scalar-ARRAY components and let every command broadcast. Evenly space N angles without the linspace endpoint duplicate: `per = \div full nn` (full = 360), `last = \sub full per`, `degs = \linspace 0 last nn` → `rads = \deg2rad degs` → `cosv = \cos rads` → `px = \mul r cosv`. Transform by a 2×2 per component: `mpx = \add t1 t2` where `t1 = \mul m11 px`, `t2 = \mul m12 py` (scalar×array broadcasts). Then one `\la-vec2d px py` draws all N arrows. - **Per-element colors**: there is NO rainbow/gradient color command (the rainbow is internal to `\la-circle-transform`). Build a color array by hand: `c0 = \text "f87171"` … `pairColors = \array c0 c1 …`, then `\set-stroke arrows pairColors` broadcasts pairwise. Reuse one color array for both halves of input→output pairs. - **Naming labels use Unicode, not LaTeX**: `\text` renders plain text only — write `V⁻¹`, `M²`, `λ₁`, `v₂` directly (never `V^-1` or `$...$`). --- # Design Principles — Aesthetics & Layout ## 0. The hierarchy of a structured visual — build in three passes A structured visual is made of three kinds of component, and you should add them in this order, one pass at a time. Do **not** interleave — finish a pass before starting the next. 1. **Primitives (geometric shapes).** Lines, circles, points, arrows, polygons, the geometry that carries the idea. Build these *first*, and use this pass to fix the **scale**: pick `unit` (§1) so the whole construction fits without crowding given the actual coordinate values. Everything downstream (label offsets, annotation anchors) depends on the scale, so settle it here. 2. **Labels.** Names attached to the primitives — `c₁` on a vector, a length on a segment, an angle at a vertex. A label identifies *which* shape is which (see §7d for anchoring a label to a moving object, §6 for reactive `\text`). 3. **Annotations.** A layer *about* the scene rather than part of it: a curly bracket, a dimension line, an arrow joining two sets, or a one-to-two-sentence text box summarizing what the viewer is looking at (§7). Placed *last*, once the geometry's extent is known, so they land in empty space and never occlude the figures (a text box goes beyond the construction's bounding box). Two forces cut across all three passes and are covered in their own sections: **draw order** (primitives that must stay visible are created *after* any backdrop — §7d) and **progressive reveal** (in interactive articles, make each pass's commands fire on the prose link that describes them — §11), so the three-pass *authoring* order and the click-by-click *reveal* order agree. --- ## 1. Scale of the canvas — the `unit` node `unit` is a built-in scalar that sets how many screen pixels one canvas unit spans. **Default: `50`.** ``` unit = \scalar 30 ``` - **Smaller `unit`** → each grid square is smaller, so *more* of the plane is visible. Use it to fit a large construction. - **Larger `unit`** → zooms in, fewer but bigger squares. Use it to focus on detail. `unit` is reactive: changing it rescales everything at once. A common pattern is to size a fixed-pixel element (like a text box) as a function of `unit` so its on-screen size stays constant while the geometry rescales. --- ## 2. Regularly spaced grids of points Build a grid by pairing a **column vector** of x-values with a **row vector** of y-values; broadcasting fills in the rest . ``` ncols = \scalar 5 nrows = \scalar 3 xs-flat = \linspace -3 3 ncols xs = \reshape xs-flat ncols 1 ys = \linspace -2 2 nrows points = \point xs ys ``` `xs` has shape `(ncols, 1)`, `ys` has shape `(nrows,)`; the result is an `ncols × nrows` grid of points. Changing `ncols` or `nrows` reflows the grid reactively. Parameterize the extent too — `xs-flat = \linspace from-x to-x ncols` — so `from-x` / `to-x` can be nudged later and the grid follows. --- ## 3. Evenly spaced points and angles on a circle Two approaches: **Use the macro** `\get-uniform-points-on-circle 6` (defined in the geometry macro library). It introduces helper variables (`n`, `r`, `radians`, `points`, …); changing `n` and `r` updates the count and radius. Because those names enter your namespace, **watch for clashes** with variables you've already defined. **Or roll your own** with `\linspace` for full control over names: ``` n = \scalar 6 angles-deg = \linspace 0 360 n angles = \deg2rad angles-deg r = \scalar 2 cos = \cos angles sin = \sin angles xs = \mul r cos ys = \mul r sin points = \point xs ys ``` Each trig step is its own line — no nesting. The points come from `r·cos(θ)` and `r·sin(θ)`, and `n`/`r` stay editable. This is also the backbone of spirals, cardioids, and any radial layout. --- ## 4. Reducing clutter A clean canvas shows only what matters. Tools, from gentlest to most destructive: - **`\hide id ...`** — make elements invisible without deleting them. Ideal for *elementary by-products*: e.g. to scatter points evenly, run `\regular-polygon` and then `\hide` the polygon's edges, keeping only the vertices. You can't `\remove` such by-products because removal cascades through reactive dependents — `\hide` sidesteps that. - **`\show id ...`** — bring hidden elements back. - **`\remove id ...`** — permanently delete a node *and everything downstream of it*. Only for things nothing else depends on. - **`\clear`** — wipe the entire canvas and store, like erasing a board to start fresh. In interactive articles `\clear` also **bounds the click-replay window** (see §11): prefer starting each independent sub-demo with its own `\clear` (re-defining shared helpers like color nodes) over `\hide`-ing the previous scene — it wipes the board *and* keeps every later click from replaying a pile of irrelevant commands. Never reference pre-`\clear` node names afterwards. ## 5. Directing attention — `\highlight` `\highlight node ...` emphasizes one or more nodes to draw the viewer's eye. It's defined for most (not all) node types, including whole Arrays. Use it to spotlight the result of a construction, a key intersection, or a point the user is meant to drag. Highlight the **object of the sentence, not the construction tool or scenery**: in "the input points sampled on a circle", highlight the points array — the circle is just how the points were made. And when the idea being illustrated is a *mapping*, don't merely flash each set: **join** inputs to outputs (`\annotate-arrow pts-in q-out`) so the correspondence itself is drawn. `\highlight` is a **transient flash** — it emphasizes for a fixed duration and then auto-clears, so there is no "un-highlight" to manage and two successive highlights never leave both sets stuck lit. That makes it ideal for **highlight-on-mention**: make the very command-link that *names* an object the one that highlights it (`{both row vectors, r₁ and r₂}(\highlight rows)`), rather than a separate "click to highlight" step. --- ## 6. Reactive text labels — `\text` with interpolation `\text` is the **only** command that accepts a double-quoted string, and it supports `${...}` interpolation referencing node ids. The label re-renders whenever those nodes change: ``` \line a b d = \distance a b label = \text "Length of this line is ${d}" ``` Move `a` or `b` and the printed distance updates live. This turns static diagrams into self-describing, responsive figures. --- ## 7. Text boxes & annotations — predictable layout `\annotate-text-box` is best when you want a label with a flexible yet predictable footprint. Its width/height semantics: ``` w=0, h=0 → no bounded box, single-line label (default) w>0, h>0 → fixed box w>0, h=-1 → fixed width, height grows to fit wrapped text w=-1, h>0 → fixed height, width sized to fit the text in that many lines w=-1, h=-1 → both auto, each capped at 5 canvas units ``` So `w` positive with `h=-1` fixes the width and lets height follow the text; swap them for a fixed-height, flexible-width box. Combined with reactive text: ``` \line a b d = \distance a b label = \text "Length of this line is ${d}" \annotate-text-box label p font w h font = \scalar 10 h = \scalar -1 \hide p \translate p -3 2 a = \point 0 0 ``` To keep a text box's *on-screen* size constant as the user zooms, define its `w` or `h` as a function of `unit` (so the absolute value stays fixed). Other annotations available (see the Annotations section of the Function Reference): `\annotate-arrow` (`p1 p2 padding label` — padding 0–0.5 trims both ends so tips stand off the points), `\annotate-curved-arrow` (`p1 p2 control padding label` — a quadratic Bezier bending toward a hidden control point; two such arrows with *opposite* controls make a clean round-trip loop between two points, e.g. an `M` / `M⁻¹` pair labelled "M" and "?"), `\annotate-curly-bracket`, `\annotate-dim-line`, `\annotate-angle-mark`, `\annotate-leader-line`, `\annotate-pin`. Use these to label distances, angles, and points instead of bare geometry — they read as a finished diagram. Labels are Text nodes. --- ## 7b. Color as differentiation Color is the cheapest way to say "these two sets are different things" — e.g. input points vs their mapped outputs, or a family of same-direction vectors vs the odd one out. Define colors once as `\text` nodes holding a bare hex (no `#`), then apply: ``` blue = \text "6aa8ff" pink = \text "F472B6" \set-fill pts-in blue \set-fill q-out pink ``` - **Points take `\set-fill`** — `\set-stroke` has no visible effect on them. - **Lines, arrows, circles, curves take `\set-stroke`.** - Both broadcast over Arrays, so one command colors a whole point set. - **Polygons**: `\set-stroke` on the Polygon node does NOT color its edges. Collect its auxiliary edge Lines into an array and stroke that: `edges = \array line0 line1 line2 line3` then `\set-stroke edges grey`. Auxiliary line auto-names count globally within a scene (first 4-gon → `line0–3`, the next → `line4–7`). - Keep prose and canvas in sync: if the canvas colors a set pink, the KaTeX in the prose can use the matching macro (`\pink{...}`) — but don't color the prose *instead of* the canvas. - **Color by *role/pair*, not per individual object.** When several objects come in input→output pairs (a point `p` and its image `Mp`; a normal `q` and its image `Nq`), give *each pair one shared color* rather than a distinct color per arrow. Two pairs → two colors, not four. The shared hue *is* the statement "these two correspond"; a fresh color per vector just adds noise and makes the reader hunt for the correspondence. (When instead you are contrasting a whole *input set* against a whole *output set* as two groups, colour the two groups — that is the pairing at the group level.) - **Over multicolored scenery, go neutral, not colored.** Color differentiates only against a plain background. When the foreground sits on top of something already multicolored — e.g. the rainbow fan from `\la-circle-transform` — a colored vector (grey/blue/pink) just blends into the noise. Stroke the foreground **white** (`white = \text "e8e8e8"`) so it reads cleanly, and lean on *labels* (`c₁`, `r₂`) rather than hue to tell the pieces apart. --- ## 7d. Draw order controls occlusion; labelling moving objects **Paint order = creation order.** A node created later is drawn on top of one created earlier. So if you draw a fan/region/curve *after* the vectors it is meant to sit behind, it paints over them and they vanish. Two consequences: - **Create backdrops first, foreground last.** Build the scenery (the `\la-circle-transform` fan, a filled region, grid lines) *before* the vectors, points, and labels that should stay visible on top. - **To reveal a backdrop *later* in the narrative, create it early and `\hide` it, then `\show` it at the reveal link.** `\show` does not re-stack — the node keeps its original (earlier) z-position — so it appears *beneath* the foreground created after it, exactly as wanted. Reordering creation is the lever; `\hide`/`\show` only controls *when* it becomes visible, not its depth. - Fewer spokes also help: drop `\la-circle-transform … n` from 40 to ~20 so the fan reads as an ellipse without a dense wall of lines swallowing the vectors. **Label a moving vector by anchoring a text box at a scaled multiple of its reactive tip.** Multiply the tip coordinates by ~`1.2` (`klab = \scalar 1.2`; `q = \mul klab tipx`) and anchor an `\annotate-text-box` at that point: the label rides just *beyond* the arrowhead (and just *outside* a bounding ellipse) and moves with the tip as a scalar animates. Reuse the scaled coordinates across several labels (`pc1 = \point q1 q2`, `pr1 = \point q1 q3`) instead of recomputing per label. `\annotate-text-box` **accepts `\set-fill`** to color its text, so label color is independent of the vector's stroke. --- ## 7c. Broadcasting is the default idiom for bulk visuals Never fan out with `\get-array-element` plus per-element commands — it is inefficient, hardcodes the element count, and bloats the command list. Commands broadcast: - **pairwise over two arrays**: `arrows = \annotate-arrow pts-in q-out` draws *every* input→output arrow in one command — and `\hide arrows` / `\show arrows` toggles them all; - **node × array**: `spokes = \line p0 q-out` draws a line from one point to every point of an array; - styling and visibility (`\set-fill`, `\set-stroke`, `\highlight`, `\hide`, `\show`) all accept arrays. Reserve `\get-array-element` for when a *single* element is genuinely needed (e.g. two endpoints for one `\line`, or vertices for a `\polygon`). A corollary: counts stay tweakable (`\circular-arange 40 r` → change 40 freely) because nothing downstream hardcodes n. --- ## 8. Moving things around — `\translate` and `\rotate` `\translate obj dx dy` and `\rotate obj center angle` reposition objects. Two uses: 1. **Composition / de-cluttering** — nudge a label or a sub-figure out of the way so nothing overlaps. 2. **Animation** — when applied to an animated parameter, they produce motion. ## 9. Animation — `\animate` `\animate scalar toValue` drives a scalar smoothly to a target. The trick to a rich animation is to **build the whole construction so it depends on one scalar**, then animate that scalar — the entire setup moves with it. See the Peaucellier-Lipkin linkage macro for a worked example. `animation-speed` (default `0.05`) controls the pace. **Never hardcode an animation's target angle.** Derive it from the geometry so it stays correct when values change: `\la-vec2d-radian x y` gives a vector's angle in radians (and `\rad2deg` converts it when the driver scalar is in degrees). Animate the *free* driver scalar to that computed target, not a magic number. **Two co-moving sets → animate twice, focusing once each.** When one animation makes two things move at once (e.g. columns spin *while* row tips glide), it is hard to watch both. Run the same animation in two passes, each preceded by a `\highlight` on a different set and prose asking the reader to follow that one. Exploit periodicity: a full turn is 2π-periodic, so animating `t` from `6.283` to `12.566` starts the second pass from the *same pose* as `0` — equivalent to resetting, but forward (`\animate` mutates the scalar in place by id, so this is safe; a literal `t = \scalar 0` reassignment is riskier). --- ## 10. Running a command without showing it To execute a command but keep it out of the article's visible bullet list, use the empty-label form: `{}(command)`. Useful for setup that should run silently (e.g. setting `unit`, hiding scaffolding) without cluttering the prose. **Attach links to the prose that already exists; do not add a second sentence that restates it.** If the text already says "Take several points `p` on a circle and attach a normal `q`", wrap the command-links *around those words* (`Take {several points $p$…}(\set-stroke pvecs …) and attach {a normal $q$}(…)`) rather than writing a fresh "Draw seven vectors `p`, then their normals `q`…" paragraph underneath. The interactive commands should *annotate* the existing explanation, not duplicate it. To make this possible, put the hidden `{}()` setup **before** that sentence, so the visible words in it can be the links. --- ## 11. Sequencing in interactive articles In an interactive article, clicking a command link executes **all commands from the last `\clear` up to that link**, resuming from the last executed index. Three rules follow: 1. **Hidden run-ups fire with the next visible link.** All the invisible `{}(...)` commands between two visible links run when the *next* visible link is clicked. So a hidden run-up must never contain something meant to appear only on a *later* click — anything staged early has to be created and `\hide`-n in the same run. Pair each hidden action with the visible link it belongs to (e.g. hidden `\hide fwd-arrows` immediately before `{Show the inverse}(\show back-arrows)`). 2. **`\clear` bounds the replay.** Long scenes make every click replay a pile of irrelevant commands; when a sub-demo doesn't need the earlier nodes, start it with a fresh `\clear` and re-define shared helpers (color nodes, `unit`). 3. **Progressive reveal beats pre-draw + highlight.** Make the prose phrases *be* the commands that produce what they describe: "each input point" highlights the inputs, "we calculate the output point" highlights the outputs, and "draw an arrow from each input to its output" *creates* the arrows at that click. Don't pre-draw the whole scene and then merely highlight one shape — it adds nothing to the idea being presented. Prefer one broadcast command at the visible link over staging many singles inline. --- ## Design checklist - [ ] Is `unit` set so the whole construction fits without crowding? - [ ] Are scaffolding by-products `\hide`-n rather than left visible? - [ ] Does the key result stand out (`\highlight`, color, or annotation)? - [ ] Do highlights land on the object of the sentence (not scenery), and are mappings shown by joining inputs to outputs? - [ ] Are distinct sets (inputs vs outputs) separated by color — `\set-fill` for points, `\set-stroke` for strokes (or **white + labels** when the foreground sits on multicolored scenery)? - [ ] Is anything meant to stay visible created *after* the backdrop it sits on (paint order = creation order), using create-early-`\hide`-then-`\show` for a backdrop revealed later? - [ ] If two things move together, is the animation run in focused passes with a `\highlight` on one set each time? - [ ] Are bulk visuals built with broadcasting rather than `\get-array-element` fan-outs (so counts stay tweakable)? - [ ] Are measurements labelled with reactive `\text` / `\annotate-*` rather than left for the viewer to infer? - [ ] Do labels and boxes stay readable when `unit` changes (sized off `unit`)? - [ ] If animated, does a single scalar drive the whole setup? - [ ] Does each independent sub-demo start with its own `\clear`, and does every hidden run-up belong to the visible link that follows it?