figureone 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms-full.txt ADDED
@@ -0,0 +1,2499 @@
1
+ # FigureOne - Complete API Reference
2
+ > For a quick overview, see llms.txt
3
+
4
+ FigureOne is a JavaScript library for drawing, animating, and interacting with shapes, text, plots, and equations in a browser using WebGL. `Fig` is the global entry point. `new Fig.Figure()` creates a figure attached to `<div id="figureOneContainer">`. Elements are added with `figure.add({ make: 'type', ...options })`.
5
+
6
+ Colors are `[r, g, b, a]` with values 0-1. Default scene is `[-1, -1, 2, 2]` (x, y, width, height).
7
+
8
+ ---
9
+
10
+ ## 1. Figure Class
11
+
12
+ ### OBJ_Figure
13
+
14
+ | Property | Type | Default | Description |
15
+ |---|---|---|---|
16
+ | htmlId | string | `'figureOneContainer'` | HTML div id to attach figure to |
17
+ | scene | OBJ_Scene \| [x,y,w,h] | `[-1, -1, 2, 2]` | Scene definition (2D rect or full scene object) |
18
+ | color | TypeColor | `[0, 0, 0, 1]` | Default shape color |
19
+ | font | OBJ_Font | `{family:'Times New Roman', size:0.2}` | Default font |
20
+ | lineWidth | number | | Default line width |
21
+ | length | number | | Default primary dimension |
22
+ | backgroundColor | TypeColor | | Background color |
23
+
24
+ ### Key Figure Methods
25
+
26
+ ```js
27
+ const figure = new Fig.Figure({ htmlId: 'myDiv', scene: [-2, -2, 4, 4] });
28
+
29
+ // Add single element (returns the element)
30
+ const elem = figure.add({ make: 'polygon', sides: 6, radius: 0.5 });
31
+
32
+ // Add multiple elements (returns array)
33
+ const [a, b] = figure.add([
34
+ { make: 'polygon', sides: 4, radius: 0.3, name: 'sq' },
35
+ { make: 'polygon', sides: 100, radius: 0.2, name: 'circ' },
36
+ ]);
37
+
38
+ // Access elements
39
+ figure.elements._sq;
40
+ figure.getElement('sq');
41
+
42
+ // Control
43
+ figure.stop(); // Stop all animations
44
+ figure.resize(); // Resize after container change
45
+ figure.animateNextFrame(); // Request next animation frame
46
+ figure.isAnimating(); // Check if any animation running
47
+
48
+ // Coordinate transforms
49
+ figure.transformPoint(point, fromSpace, toSpace);
50
+ figure.spaceTransformMatrix(fromSpace, toSpace);
51
+
52
+ // SlideNavigator
53
+ const nav = figure.addSlideNavigator({ slides: [...] });
54
+ ```
55
+
56
+ ---
57
+
58
+ ## 2. Common Base Types
59
+
60
+ ### TypeColor
61
+ `[r, g, b, a]` — array of 4 numbers, each 0-1. Example: `[1, 0, 0, 1]` = red.
62
+
63
+ ### TypeParsablePoint
64
+ Any of: `[x, y]`, `[x, y, z]`, `new Fig.Point(x, y)`, or a Point instance.
65
+
66
+ ### TypeParsableTransform
67
+ Any of: a Transform instance, or an array of transform components:
68
+ - Translation: `['t', x, y, z]`
69
+ - Rotation 2D: `['r', angle]`
70
+ - Rotation 3D: `['r', angle, axisX, axisY, axisZ]`
71
+ - Scale: `['s', sx, sy, sz]`
72
+ - Direction: `['d', dx, dy, dz]`
73
+
74
+ Example: `[['t', 0.5, 0], ['r', Math.PI/4], ['s', 2, 2]]`
75
+
76
+ ### TypeElementPath
77
+ Path to figure element(s) within a collection. Supports multiple formats:
78
+ - String dot-path: `'parent.child.grandchild'`
79
+ - Direct reference: a FigureElement instance
80
+ - Object paths: `{ parent: ['child.a', 'child.b'] }`
81
+ - Array of any above: `['elem1', 'elem2', figureElement]`
82
+
83
+ ### OBJ_Font
84
+
85
+ | Property | Type | Default | Description |
86
+ |---|---|---|---|
87
+ | family | string | `'Times New Roman'` | Font family |
88
+ | style | `'normal'` \| `'italic'` | `'normal'` | Font style |
89
+ | weight | string \| number | `'normal'` | Font weight |
90
+ | size | number | `0.2` | Size in draw space units |
91
+ | color | TypeColor \| null | | Font color |
92
+ | underline | boolean \| object | `false` | Underline options |
93
+ | outline | boolean \| object | `false` | Outline options |
94
+ | render | `'gl'` \| `'2d'` \| `'html'` | `'gl'` | Render target |
95
+
96
+ ### OBJ_Texture
97
+
98
+ | Property | Type | Default | Description |
99
+ |---|---|---|---|
100
+ | src | string | | Image URL |
101
+ | coords | number[] | `[]` | Texture coordinates per vertex |
102
+ | mapFrom | [x,y,w,h] | `[0, 0, 1, 1]` | Image space window |
103
+ | mapTo | [x,y,w,h] | `[-1, -1, 2, 2]` | Draw space window |
104
+ | repeat | boolean | `false` | Tile image (power-of-2 only) |
105
+ | loadColor | TypeColor | `[0, 0, 1, 0.5]` | Color while loading |
106
+ | onLoad | () => void | | Callback after load |
107
+
108
+ ### OBJ_FigurePrimitive (base for all primitives)
109
+
110
+ | Property | Type | Default | Description |
111
+ |---|---|---|---|
112
+ | name | string | | Element name |
113
+ | position | TypeParsablePoint | | Shorthand for translation |
114
+ | transform | TypeParsableTransform | | Full transform |
115
+ | color | TypeColor | | Element color |
116
+ | touch | boolean \| OBJ_Touch | `false` | Enable touch |
117
+ | move | boolean \| OBJ_ElementMove | `false` | Enable dragging |
118
+ | dimColor | TypeColor | | Color when dimmed |
119
+ | defaultColor | TypeColor | | Color when undimmed |
120
+ | scenarios | OBJ_Scenarios | | Named presets |
121
+ | scene | Scene \| OBJ_Scene | | Custom scene |
122
+
123
+ ### OBJ_Collection
124
+
125
+ | Property | Type | Default | Description |
126
+ |---|---|---|---|
127
+ | transform | TypeParsableTransform | | Collection transform |
128
+ | position | TypeParsablePoint | | Overwrites transform translation |
129
+ | color | TypeColor | | Default color |
130
+ | parent | FigureElement \| null | | Parent element |
131
+ | border | string \| number | `'children'` | Border: `'children'`, `'rect'`, or number buffer |
132
+ | touchBorder | string \| number | `'children'` | Touch border |
133
+
134
+ ---
135
+
136
+ ## 3. 2D Shape Primitives
137
+
138
+ All 2D primitives extend OBJ_FigurePrimitive. Properties from OBJ_FigurePrimitive (name, position, color, etc.) are always available.
139
+
140
+ ### OBJ_Generic (`make: 'generic'`)
141
+
142
+ | Property | Type | Default | Description |
143
+ |---|---|---|---|
144
+ | points | TypeParsablePoint[] | | Vertex positions |
145
+ | drawType | `'TRIANGLES'` \| `'POINTS'` \| `'FAN'` \| `'STRIP'` \| `'LINES'` | `'TRIANGLES'` | GL primitive type |
146
+ | copy | CPY_Step[] \| CPY_Step | `[]` | Copy/repeat pattern |
147
+ | texture | OBJ_Texture | | Texture fill |
148
+ | drawBorder | TypeParsableBorder | | Shape border points |
149
+ | drawBorderBuffer | TypeParsableBorder | | Buffer border points |
150
+ | border | string \| number | `'draw'` | `'draw'`, `'buffer'`, `'rect'`, or number |
151
+ | touchBorder | string \| number | `'border'` | `'border'`, `'draw'`, `'buffer'`, `'rect'`, or number |
152
+ | pulse | number | | Default pulse scale |
153
+
154
+ ### CPY_Step (copy pattern)
155
+
156
+ | Property | Type | Default | Description |
157
+ |---|---|---|---|
158
+ | along | `'x'` \| `'y'` \| `'z'` \| `'rotation'` | | Copy direction |
159
+ | num | number | | Number of copies |
160
+ | step | number | | Step distance or angle |
161
+ | to | TypeParsablePoint \| Transform | | Copy to position/transform |
162
+ | start | number | `0` | Start index |
163
+ | end | number | | End index |
164
+ | original | boolean | `true` | Include original |
165
+
166
+ ### OBJ_Polyline (`make: 'polyline'`)
167
+ Extends OBJ_Generic (without drawType).
168
+
169
+ | Property | Type | Default | Description |
170
+ |---|---|---|---|
171
+ | points | TypeParsablePoint[] | | Corner points |
172
+ | width | number | `0.01` | Line width |
173
+ | close | boolean | `false` | Close polyline |
174
+ | simple | boolean | `false` | Simple mode (fast, fewer features) |
175
+ | widthIs | `'mid'` \| `'outside'` \| `'inside'` \| `'positive'` \| `'negative'` \| number | `'mid'` | Width growth direction |
176
+ | cornerStyle | `'auto'` \| `'none'` \| `'radius'` \| `'fill'` | `'auto'` | Corner rendering |
177
+ | cornerSize | number | `0.01` | Corner radius (when `'radius'`) |
178
+ | cornerSides | number | `10` | Sides in corner curve |
179
+ | cornersOnly | boolean | `false` | Draw only corners |
180
+ | cornerLength | number | `0.1` | Corner length when cornersOnly |
181
+ | minAutoCornerAngle | number | `π/7` | Threshold for auto corners |
182
+ | dash | number[] | | Dash pattern: `[lineLen, gapLen, ...]` |
183
+ | arrow | OBJ_LineArrows \| TypeArrowHead | | Arrow on ends |
184
+ | linePrimitives | boolean | `false` | Use GL line primitives |
185
+
186
+ ### OBJ_Polygon (`make: 'polygon'`)
187
+ Extends OBJ_Generic (without drawType).
188
+
189
+ | Property | Type | Default | Description |
190
+ |---|---|---|---|
191
+ | sides | number | `4` | Number of sides |
192
+ | radius | number | `1` | Radius |
193
+ | rotation | number | `0` | Shape rotation (vertex definition) |
194
+ | offset | TypeParsablePoint | `[0, 0]` | Center offset |
195
+ | sidesToDraw | number | all | Number of sides to draw |
196
+ | angleToDraw | number | `2π` | Angle to draw |
197
+ | direction | `1` \| `-1` | `1` | 1=CCW, -1=CW |
198
+ | line | OBJ_LineStyleSimple | | Line style (outline mode) |
199
+
200
+ ### OBJ_Star (`make: 'star'`)
201
+ Extends OBJ_Generic (without drawType).
202
+
203
+ | Property | Type | Default | Description |
204
+ |---|---|---|---|
205
+ | sides | number | `4` | Number of points |
206
+ | radius | number | `1` | Outer radius |
207
+ | innerRadius | number | `radius/2` | Inner radius |
208
+ | rotation | number | `0` | Shape rotation |
209
+ | offset | TypeParsablePoint | `[0, 0]` | Center offset |
210
+ | line | OBJ_LineStyleSimple | | Line style |
211
+
212
+ ### OBJ_Rectangle (`make: 'rectangle'`)
213
+ Extends OBJ_Generic (without drawType).
214
+
215
+ | Property | Type | Default | Description |
216
+ |---|---|---|---|
217
+ | width | number | `1` | Width |
218
+ | height | number | `1` | Height |
219
+ | xAlign | `'left'` \| `'center'` \| `'right'` \| number | `'center'` | Horizontal alignment |
220
+ | yAlign | `'bottom'` \| `'middle'` \| `'top'` \| number | `'middle'` | Vertical alignment |
221
+ | corner | OBJ_CurvedCorner | | Rounded corners: `{ radius, sides }` |
222
+ | line | OBJ_LineStyleSimple | | Line style |
223
+ | offset | TypeParsablePoint | | Position offset |
224
+
225
+ ### OBJ_Ellipse (`make: 'ellipse'`)
226
+ Extends OBJ_Generic (without drawType).
227
+
228
+ | Property | Type | Default | Description |
229
+ |---|---|---|---|
230
+ | width | number | `1` | Width |
231
+ | height | number | `1` | Height |
232
+ | xAlign | `'left'` \| `'center'` \| `'right'` \| number | `'center'` | Horizontal alignment |
233
+ | yAlign | `'bottom'` \| `'middle'` \| `'top'` \| number | `'middle'` | Vertical alignment |
234
+ | sides | number | `20` | Number of sides |
235
+ | line | OBJ_LineStyleSimple | | Line style |
236
+
237
+ ### OBJ_Arc (`make: 'arc'`)
238
+ Extends OBJ_Generic (without drawType).
239
+
240
+ | Property | Type | Default | Description |
241
+ |---|---|---|---|
242
+ | radius | number | | Radius |
243
+ | sides | number | `20` | Number of sides |
244
+ | startAngle | number | `0` | Start angle (radians) |
245
+ | angle | number | `1` | Arc angle (radians) |
246
+ | line | OBJ_LineStyleSimple | | Line style |
247
+ | fillCenter | boolean | | Fill to center point |
248
+
249
+ ### OBJ_Triangle (`make: 'triangle'`)
250
+ Extends OBJ_Generic (without drawType).
251
+
252
+ Define by one of (highest precedence first): `points`, `ASA`, `SAS`, `AAS`, `SSS`, or `width`+`height`+`top`.
253
+
254
+ | Property | Type | Default | Description |
255
+ |---|---|---|---|
256
+ | points | Point[] | | Three defining points |
257
+ | width | number | | Base width |
258
+ | height | number | | Height |
259
+ | top | `'left'` \| `'right'` \| `'center'` | `'center'` | Top vertex position |
260
+ | SSS | [s1,s2,s3] | | Side-Side-Side |
261
+ | ASA | [a1,s1,a2] | | Angle-Side-Angle |
262
+ | AAS | [a1,a2,s1] | | Angle-Angle-Side |
263
+ | SAS | [s1,a1,s2] | | Side-Angle-Side |
264
+ | direction | `1` \| `-1` | `1` | Triangle above (1) or below (-1) base |
265
+ | rotation | number \| `'s1'` \| `'s2'` \| `'s3'` | | Rotation angle or relative to side |
266
+ | xAlign | string | `'centroid'` | X alignment: `'left'`, `'center'`, `'right'`, `'centroid'`, `'a1'`..`'a3'`, `'s1'`..`'s3'` |
267
+ | yAlign | string | `'centroid'` | Y alignment: `'bottom'`, `'middle'`, `'top'`, `'centroid'`, `'a1'`..`'a3'`, `'s1'`..`'s3'` |
268
+ | line | OBJ_LineStyleSimple | | Line style |
269
+
270
+ ### OBJ_Line (`make: 'line'`)
271
+ Extends OBJ_Generic (without drawType).
272
+
273
+ | Property | Type | Default | Description |
274
+ |---|---|---|---|
275
+ | p1 | TypeParsablePoint | | Start point |
276
+ | p2 | TypeParsablePoint | | End point |
277
+ | length | number | | Length from p1 (alternative to p2) |
278
+ | angle | number | | Angle from p1 (alternative to p2) |
279
+ | width | number | `0.01` | Line width |
280
+ | widthIs | `'mid'` \| `'positive'` \| `'negative'` \| number | `'mid'` | Width direction |
281
+ | dash | number[] | | Dash pattern |
282
+ | arrow | OBJ_LineArrows \| TypeArrowHead | | Arrows on ends |
283
+
284
+ ### OBJ_Arrow (`make: 'arrow'`)
285
+ Extends OBJ_Generic (without drawType).
286
+
287
+ | Property | Type | Default | Description |
288
+ |---|---|---|---|
289
+ | head | TypeArrowHead | `'triangle'` | `'triangle'`, `'barb'`, `'rectangle'`, `'line'`, `'polygon'`, `'circle'`, `'bar'`, `'reverseTriangle'` |
290
+ | scale | number | | Scale default dimensions |
291
+ | length | number | | Head length along line |
292
+ | width | number | | Head width |
293
+ | rotation | number | | Polygon head rotation |
294
+ | sides | number | | Polygon/circle sides |
295
+ | radius | number | | Polygon/circle radius |
296
+ | barb | number | | Barb length |
297
+ | tailWidth | number | | Tail line width |
298
+ | tail | boolean \| number | `false` | Include tail; number = tail length |
299
+ | align | `'tip'` \| `'start'` \| `'mid'` \| `'tail'` | `'tip'` | Which part at origin |
300
+ | angle | number | `0` | Drawing angle |
301
+
302
+ ### OBJ_LineArrows (arrow config for lines/polylines)
303
+
304
+ | Property | Type | Default | Description |
305
+ |---|---|---|---|
306
+ | start | TypeArrowHead \| object | | Start arrow |
307
+ | end | TypeArrowHead \| object | | End arrow |
308
+ | scale | number | | Scale both arrows |
309
+
310
+ TypeArrowHead: `'triangle'` | `'barb'` | `'rectangle'` | `'line'` | `'polygon'` | `'circle'` | `'bar'` | `'reverseTriangle'`
311
+
312
+ ### OBJ_Grid (`make: 'grid'`)
313
+ Extends OBJ_Generic (without drawType).
314
+
315
+ | Property | Type | Default | Description |
316
+ |---|---|---|---|
317
+ | bounds | [x,y,w,h] | | Grid rectangle |
318
+ | xStep | number | | Vertical line spacing |
319
+ | yStep | number | | Horizontal line spacing |
320
+ | xNum | number | | Number of vertical lines |
321
+ | yNum | number | | Number of horizontal lines |
322
+ | line | OBJ_LineStyleSimple | | Line style |
323
+
324
+ ### OBJ_Text (`make: 'text'`)
325
+
326
+ | Property | Type | Default | Description |
327
+ |---|---|---|---|
328
+ | text | string \| string[] | | Text content |
329
+ | location | TypeParsablePoint \| TypeParsablePoint[] | `[0, 0]` | Position(s) |
330
+ | font | OBJ_Font | | Font (defaults to figure font) |
331
+ | xAlign | `'left'` \| `'center'` \| `'right'` | `'left'` | Horizontal alignment |
332
+ | yAlign | `'top'` \| `'bottom'` \| `'middle'` \| `'alphabetic'` \| `'baseline'` | `'baseline'` | Vertical alignment |
333
+
334
+ ### OBJ_LineStyleSimple (used by polygon, rectangle, etc.)
335
+
336
+ | Property | Type | Default | Description |
337
+ |---|---|---|---|
338
+ | width | number | | Line width |
339
+ | widthIs | `'mid'` \| `'outside'` \| `'inside'` \| number | `'mid'` | Width direction |
340
+ | dash | number[] | | Dash pattern |
341
+ | color | TypeColor | | Line color |
342
+
343
+ ### OBJ_GenericGL (`make: 'gl'`)
344
+
345
+ | Property | Type | Default | Description |
346
+ |---|---|---|---|
347
+ | glPrimitive | string | `'TRIANGLES'` | GL primitive type |
348
+ | vertexShader | object | | Custom vertex shader |
349
+ | fragmentShader | object | | Custom fragment shader |
350
+ | attributes | OBJ_GLAttribute[] | | Shader attributes |
351
+ | uniforms | OBJ_GLUniform[] | | Shader uniforms |
352
+ | texture | OBJ_Texture | | Texture |
353
+ | dimension | `2` \| `3` | `2` | Coordinate dimensions |
354
+ | light | `'directional'` \| `'point'` \| null | null | Lighting |
355
+ | vertices | number[] \| object | | Vertex data |
356
+ | colors | number[] \| object | | Per-vertex colors |
357
+ | normals | number[] \| object | | Normal vectors |
358
+
359
+ ### OBJ_Morph (`make: 'morph'`)
360
+
361
+ Morphable shape with multiple point arrays that can be animated between.
362
+
363
+ | Property | Type | Default | Description |
364
+ |---|---|---|---|
365
+ | name | string | | Element name |
366
+ | pointArrays | number[][] | | Array of flat vertex arrays (each: [x1,y1,x2,y2,...]) |
367
+ | color | TypeColor \| TypeColor[] | | Color(s) per point array |
368
+ | names | string[] | | Name for each point array (usable in morph animation) |
369
+ | glPrimitive | `'TRIANGLES'` \| `'POINTS'` \| `'FAN'` \| `'STRIP'` \| `'LINES'` | `'TRIANGLES'` | GL primitive type |
370
+
371
+ ```js
372
+ // Simple example
373
+ figure.add({
374
+ make: 'gl',
375
+ vertices: [0, 0, 0.5, 0, 0, 0.5],
376
+ color: [1, 0, 0, 1],
377
+ });
378
+ ```
379
+
380
+ ---
381
+
382
+ ## 4. 3D Shape Primitives
383
+
384
+ All 3D primitives extend OBJ_FigurePrimitive and OBJ_Generic3All.
385
+
386
+ ### OBJ_Generic3All (base for all 3D shapes)
387
+
388
+ | Property | Type | Default | Description |
389
+ |---|---|---|---|
390
+ | light | `'directional'` \| `'point'` \| `'ambient'` \| null | `'directional'` | Lighting type |
391
+ | copy | CPY_Step[] \| CPY_Step | | Copy pattern |
392
+ | usage | `'STATIC'` \| `'DYNAMIC'` | `'STATIC'` | Buffer usage |
393
+ | touchScale | number \| TypeParsablePoint | | Touch scale |
394
+
395
+ ### OBJ_Sphere (`make: 'sphere'`)
396
+
397
+ | Property | Type | Default | Description |
398
+ |---|---|---|---|
399
+ | sides | number | `10` | Sides around half great circle |
400
+ | radius | number | `1` | Sphere radius |
401
+ | normals | `'curve'` \| `'flat'` | `'flat'` | Shading style |
402
+ | center | TypeParsablePoint | `[0, 0]` | Center position |
403
+ | lines | boolean | `false` | Wire mesh mode |
404
+
405
+ ### OBJ_Cube (`make: 'cube'`)
406
+
407
+ | Property | Type | Default | Description |
408
+ |---|---|---|---|
409
+ | side | number | `1` | Side length |
410
+ | center | TypeParsablePoint | `[0, 0]` | Center position |
411
+ | lines | boolean | `false` | Wire mesh mode |
412
+
413
+ ### OBJ_Cylinder (`make: 'cylinder'`)
414
+
415
+ | Property | Type | Default | Description |
416
+ |---|---|---|---|
417
+ | sides | number | `10` | Number of sides |
418
+ | radius | number | `1` | Radius |
419
+ | normals | `'curve'` \| `'flat'` | `'flat'` | Shading style |
420
+ | line | [p1, p2] | | Position/orient via two points |
421
+ | length | number | `1` | Length (if `line` not defined) |
422
+ | ends | boolean \| `1` \| `2` | `true` | Fill ends: true=both, false=none, 1=first, 2=second |
423
+ | rotation | number | `0` | Base rotation |
424
+
425
+ ### OBJ_Line3 (`make: 'line3'`)
426
+
427
+ | Property | Type | Default | Description |
428
+ |---|---|---|---|
429
+ | sides | number | `10` | Number of sides |
430
+ | p1 | TypeParsablePoint | `[0, 0, 0]` | Start point |
431
+ | p2 | TypeParsablePoint | `p1 + [1, 0, 0]` | End point |
432
+ | width | number | | Line width |
433
+ | arrow | OBJ_Line3Arrow \| boolean | | Arrows on ends |
434
+ | rotation | number | `0` | Rotation around axis |
435
+ | normals | `'curve'` \| `'flat'` | `'curve'` | Shading style |
436
+
437
+ ### OBJ_Cone (`make: 'cone'`)
438
+
439
+ | Property | Type | Default | Description |
440
+ |---|---|---|---|
441
+ | sides | number | `10` | Number of sides |
442
+ | radius | number | | Base radius |
443
+ | normals | `'curve'` \| `'flat'` | `'flat'` | Shading style |
444
+ | line | [base, tip] | | Position/orient via two points |
445
+ | length | number | `1` | Length (if `line` not defined) |
446
+ | rotation | number | `0` | Base rotation |
447
+ | lines | boolean | `false` | Wire mesh mode |
448
+
449
+ ### OBJ_Prism (`make: 'prism'`)
450
+
451
+ | Property | Type | Default | Description |
452
+ |---|---|---|---|
453
+ | base | TypeParsablePoint[] | | Base polygon points (XY plane, CCW) |
454
+ | baseTriangles | TypeParsablePoint[] | | Base triangulation (for non-convex) |
455
+ | length | number | | Prism length (into +z) |
456
+ | lines | boolean | `false` | Wire mesh mode |
457
+
458
+ ### OBJ_Revolve (`make: 'revolve'`)
459
+
460
+ | Property | Type | Default | Description |
461
+ |---|---|---|---|
462
+ | profile | TypeParsablePoint[] | | XY plane profile (y >= 0) |
463
+ | sides | number | | Radial sweep sides |
464
+ | normals | `'flat'` \| `'curveProfile'` \| `'curveRadial'` \| `'curve'` | `'flat'` | Shading style |
465
+ | axis | TypeParsablePoint | | Orient shape axis |
466
+ | rotation | number | | Initial sweep angle |
467
+ | lines | boolean | `false` | Wire mesh mode |
468
+
469
+ ### OBJ_Surface (`make: 'surface'`)
470
+
471
+ | Property | Type | Default | Description |
472
+ |---|---|---|---|
473
+ | points | TypeParsablePoint[][] | | 2D grid of 3D points |
474
+ | normals | `'flat'` \| `'curveColumns'` \| `'curveRows'` \| `'curve'` | `'flat'` | Shading style |
475
+ | closeRows | boolean | `false` | First/last row same (for curved normals) |
476
+ | closeColumns | boolean | `false` | First/last column same |
477
+ | lines | boolean | `false` | Wire mesh mode |
478
+ | invertNormals | boolean | | Invert all normals |
479
+
480
+ ### OBJ_CameraControl (`make: 'cameraControl'`)
481
+
482
+ | Property | Type | Default | Description |
483
+ |---|---|---|---|
484
+ | left | number | `0` | Screen left (0-1) |
485
+ | bottom | number | `0` | Screen bottom (0-1) |
486
+ | width | number | `1` | Width (0-1) |
487
+ | height | number | `1` | Height (0-1) |
488
+ | axis | TypeParsablePoint | `[0, 1, 0]` | Vertical axis |
489
+ | controlScene | Scene \| string | | Scene to control (default: figure scene) |
490
+ | sensitivity | number | `5` | Overall sensitivity |
491
+ | xSensitivity | number | `1` | Horizontal sensitivity (0 = no azimuth) |
492
+ | ySensitivity | number | `1` | Vertical sensitivity (0 = no elevation) |
493
+ | back | boolean | `true` | Priority behind other touchable elements |
494
+
495
+ ```js
496
+ // 3D setup
497
+ const figure = new Fig.Figure({ scene: { style: 'orthographic' } });
498
+ figure.add([
499
+ { make: 'sphere', radius: 0.3, color: [1, 0, 0, 1], normals: 'curve' },
500
+ { make: 'cameraControl' },
501
+ ]);
502
+
503
+ // Surface from function
504
+ const points = Fig.surfaceGrid({
505
+ x: [-0.8, 0.8, 0.02], y: [-0.8, 0.8, 0.02],
506
+ z: (x, y) => Math.sin(Math.sqrt(x*x + y*y) * 4) * 0.3,
507
+ });
508
+ figure.add({ make: 'surface', points, color: [1, 0, 0, 1], normals: 'curve' });
509
+ ```
510
+
511
+ ---
512
+
513
+ ## 5. Collections
514
+
515
+ Collections are higher-level compound elements. Use `make: 'collections.type'`.
516
+
517
+ ### COL_Plot (`make: 'collections.plot'`)
518
+
519
+ | Property | Type | Default | Description |
520
+ |---|---|---|---|
521
+ | width | number | | Plot area width |
522
+ | height | number | | Plot area height |
523
+ | x | object \| boolean | | X axis config or `false` to hide |
524
+ | y | object \| boolean | | Y axis config or `false` to hide |
525
+ | trace | COL_Trace \| COL_Trace[] \| point[] | | Trace data |
526
+ | legend | COL_PlotLegend | | Legend options |
527
+ | title | string \| object | | Plot title |
528
+ | grid | boolean | | Show grid |
529
+ | cross | TypeParsablePoint \| null | | Where axes cross |
530
+ | frame | boolean \| TypeColor \| object | | Frame around plot |
531
+ | plotArea | TypeColor \| object | | Plot area background |
532
+ | font | OBJ_Font | | Default font |
533
+ | color | TypeColor | | Default color |
534
+ | position | TypeParsablePoint | | Position |
535
+ | zoom | object \| `'x'` \| `'y'` \| `'xy'` | | Zoom options |
536
+ | pan | object \| `'x'` \| `'y'` \| `'xy'` | | Pan options |
537
+ | styleTheme | `'box'` \| `'numberLine'` \| `'positiveNumberLine'` | `'box'` | Theme |
538
+ | colorTheme | `'light'` \| `'dark'` | `'dark'` | Color theme |
539
+ | autoGrid | boolean | `true` | Expand grid across plot |
540
+
541
+ ### COL_Trace (used within COL_Plot)
542
+
543
+ | Property | Type | Default | Description |
544
+ |---|---|---|---|
545
+ | points | TypeParsablePoint[] | | Trace data points |
546
+ | x | number[] | | X values (alternative to points) |
547
+ | y | number[] | | Y values (alternative to points) |
548
+ | line | OBJ_LineStyleSimple | | Line style |
549
+ | markers | OBJ_Polygon \| OBJ_Star | | Marker shape |
550
+ | color | TypeColor | | Trace color |
551
+ | name | string | | Name for legend |
552
+
553
+ ### COL_PlotLegend
554
+
555
+ | Property | Type | Default | Description |
556
+ |---|---|---|---|
557
+ | position | TypeParsablePoint | | Legend position |
558
+ | length | number | | Line sample length |
559
+ | space | number | | Space between line and text |
560
+ | offset | TypeParsablePoint | | Offset between traces |
561
+ | font | OBJ_Font | | Legend font |
562
+ | fontColorIsLineColor | boolean | | Match text to line color |
563
+ | frame | object | | Frame around legend |
564
+
565
+ ```js
566
+ // Plot with multiple traces
567
+ figure.add({
568
+ make: 'collections.plot',
569
+ width: 1, height: 1, position: [-0.5, -0.5],
570
+ x: { title: 'time (s)' },
571
+ y: { start: 0, stop: 100, title: 'distance (m)' },
572
+ trace: [
573
+ { points: data1, name: 'Linear' },
574
+ { points: data2, name: 'Quadratic', markers: { sides: 4, radius: 0.02 } },
575
+ ],
576
+ legend: { font: { size: 0.05 } },
577
+ });
578
+ ```
579
+
580
+ ### COL_Axis (`make: 'collections.axis'`)
581
+
582
+ | Property | Type | Default | Description |
583
+ |---|---|---|---|
584
+ | axis | `'x'` \| `'y'` | `'x'` | Orientation |
585
+ | length | number | | Axis length |
586
+ | start | number | `0` | Start value |
587
+ | stop | number | `start + 1` | Stop value |
588
+ | step | number | | Tick/label step |
589
+ | line | boolean \| object | | Axis line |
590
+ | ticks | object \| boolean | `false` | Tick marks |
591
+ | labels | object \| boolean \| string | | Axis labels |
592
+ | grid | object \| boolean | `false` | Grid lines |
593
+ | title | string \| object | | Axis title |
594
+ | font | OBJ_Font | | Default font |
595
+ | show | boolean | `true` | Show/hide axis |
596
+ | min | number \| null | `null` | Min value (zoom/pan limit) |
597
+ | max | number \| null | `null` | Max value (zoom/pan limit) |
598
+ | position | TypeParsablePoint | `[0, 0]` | Axis position |
599
+ | values | number[] | | Custom tick/label values |
600
+ | auto | [number, number] | | Auto-select start/stop/step from data range |
601
+
602
+ #### Axis Ticks Configuration
603
+ When `ticks` is an object:
604
+
605
+ | Property | Type | Default | Description |
606
+ |---|---|---|---|
607
+ | step | number | | Tick step (overrides axis step) |
608
+ | start | number | | Start value for ticks |
609
+ | stop | number | | Stop value for ticks |
610
+ | length | number | `0.1` | Tick length |
611
+ | width | number | | Tick width |
612
+ | offset | number | `0` | Offset from axis |
613
+ | color | TypeColor | | Tick color |
614
+ | values | number[] | | Custom tick positions |
615
+
616
+ #### Axis Labels Configuration
617
+ When `labels` is an object:
618
+
619
+ | Property | Type | Default | Description |
620
+ |---|---|---|---|
621
+ | step | number | | Label step (overrides axis step) |
622
+ | start | number | | Start value for labels |
623
+ | stop | number | | Stop value for labels |
624
+ | font | OBJ_Font | | Label font |
625
+ | precision | number | | Decimal precision |
626
+ | format | function | | Custom format: `(value) => string` |
627
+ | offset | TypeParsablePoint | | Label offset |
628
+ | rotation | number | | Label rotation |
629
+ | values | number[] | | Custom label positions |
630
+ | hide | number[] | | Values to hide labels for |
631
+ | text | string[] | | Custom text per label position |
632
+
633
+ #### Axis Grid Configuration
634
+ When `grid` is an object:
635
+
636
+ | Property | Type | Default | Description |
637
+ |---|---|---|---|
638
+ | step | number | | Grid step |
639
+ | length | number | | Grid line length |
640
+ | width | number | | Grid line width |
641
+ | color | TypeColor | | Grid color |
642
+ | dash | number[] | | Dash pattern |
643
+
644
+ ```js
645
+ // Detailed axis example
646
+ figure.add({
647
+ make: 'collections.axis',
648
+ axis: 'x', length: 2, start: -5, stop: 5, step: 1,
649
+ position: [-1, 0],
650
+ ticks: { length: 0.05, offset: -0.025 },
651
+ labels: { precision: 0, offset: [0, -0.1], hide: [0] },
652
+ grid: { length: 2, width: 0.002, dash: [0.01, 0.01], color: [0.5, 0.5, 0.5, 0.3] },
653
+ title: { text: 'x', offset: [0, -0.2] },
654
+ });
655
+ ```
656
+
657
+ ### COL_Axis3 (`make: 'collections.axis3'`)
658
+
659
+ | Property | Type | Default | Description |
660
+ |---|---|---|---|
661
+ | width | number \| [x,y,z] | | Axis width |
662
+ | length | number \| [x,y,z] | | Axis length |
663
+ | start | number \| [x,y,z] | `0` | Start value |
664
+ | sides | number \| [x,y,z] | `10` | Cross-section sides |
665
+ | lines | boolean \| [x,y,z] | `false` | Wire mesh mode |
666
+ | arrow | object \| boolean | | Arrow options |
667
+ | color | TypeColor \| [x,y,z] | red, green, blue | Per-axis colors |
668
+
669
+ ### COL_Line (`make: 'collections.line'` or `'oline'`)
670
+
671
+ | Property | Type | Default | Description |
672
+ |---|---|---|---|
673
+ | p1 | TypeParsablePoint | | Start point |
674
+ | p2 | TypeParsablePoint | | End point |
675
+ | angle | number | | Line angle (alternative to p2) |
676
+ | length | number | | Line length (alternative to p2) |
677
+ | offset | number | | Line offset |
678
+ | align | `'start'` \| `'end'` \| `'center'` \| number | | Rotation center |
679
+ | width | number | | Line width |
680
+ | label | OBJ_LineLabel | | Label annotation |
681
+ | arrow | OBJ_LineArrows \| TypeArrowHead | | Arrows |
682
+ | dash | number[] | | Dash pattern |
683
+ | move | object | | Move options |
684
+
685
+ ### COL_Angle (`make: 'collections.angle'` or `'angle'`)
686
+
687
+ | Property | Type | Default | Description |
688
+ |---|---|---|---|
689
+ | position | Point | | Vertex position |
690
+ | startAngle | number | | Start angle |
691
+ | angle | number | | Angle size |
692
+ | p1 | Point | | Point on first ray (alternative) |
693
+ | p2 | Point | | Vertex point (alternative) |
694
+ | p3 | Point | | Point on second ray (alternative) |
695
+ | direction | `1` \| `-1` | | Annotation side |
696
+ | curve | object | | Curve annotation options |
697
+ | arrow | object | | Arrow annotation options |
698
+ | corner | object | | Corner drawing options |
699
+ | label | object | | Label options |
700
+
701
+ ### COL_Polyline (`make: 'collections.polyline'` or `'opolyline'`)
702
+ Extends OBJ_Polyline properties.
703
+
704
+ | Property | Type | Default | Description |
705
+ |---|---|---|---|
706
+ | showLine | boolean | `true` | Show the line |
707
+ | angle | object \| COL_Angle[] | | Angle annotations |
708
+ | side | object \| COL_Line[] | | Side annotations |
709
+ | pad | object | | Move pad options |
710
+ | makeValid | object \| null | | Shape consistency enforcement |
711
+ | font | OBJ_Font | | Default label font |
712
+
713
+ ### COL_Rectangle (`make: 'collections.rectangle'`)
714
+
715
+ | Property | Type | Default | Description |
716
+ |---|---|---|---|
717
+ | width | number | | Width |
718
+ | height | number | | Height |
719
+ | xAlign | `'left'` \| `'center'` \| `'right'` \| number | | X alignment |
720
+ | yAlign | `'bottom'` \| `'middle'` \| `'top'` \| number | | Y alignment |
721
+ | line | OBJ_LineStyleSimple | | Outline style |
722
+ | fill | TypeColor \| OBJ_Texture | | Fill |
723
+ | corner | OBJ_CurvedCorner | | Rounded corners |
724
+ | label | OBJ_FormattedText | | Label |
725
+ | button | boolean \| TypeColor | `false` | Button behavior |
726
+
727
+ ### COL_Slider (`make: 'collections.slider'`)
728
+
729
+ | Property | Type | Default | Description |
730
+ |---|---|---|---|
731
+ | width | number | | Slider width |
732
+ | height | number | | Slider height |
733
+ | barHeight | number | | Bar height |
734
+ | marker | object \| `'polygon'` \| `'rectangle'` \| `'none'` | `'polygon'` | Marker style |
735
+ | sides | number | `20` | Curve sides |
736
+ | theme | `'dark'` \| `'light'` | `'dark'` | Color theme |
737
+ | colorOff | TypeColor | | Off color |
738
+ | colorOn | TypeColor | `[0, 1, 0, 1]` | On color |
739
+
740
+ ### COL_Toggle (`make: 'collections.toggle'`)
741
+
742
+ | Property | Type | Default | Description |
743
+ |---|---|---|---|
744
+ | width | number | | Toggle width |
745
+ | height | number | | Toggle height |
746
+ | barHeight | number | | Bar height |
747
+ | sides | number | `20` | Curve sides |
748
+ | theme | `'dark'` \| `'light'` | `'dark'` | Color theme |
749
+ | colorOff | TypeColor | | Off color |
750
+ | colorOn | TypeColor | `[0, 1, 0, 1]` | On color |
751
+ | label | object | | Label options |
752
+
753
+ ### COL_Button (`make: 'collections.button'`)
754
+
755
+ | Property | Type | Default | Description |
756
+ |---|---|---|---|
757
+ | width | number | | Button width |
758
+ | height | number | | Button height |
759
+ | corner | OBJ_CurvedCorner | | Corner rounding |
760
+ | line | OBJ_LineStyleSimple \| null | | Outline |
761
+ | label | object | | Label |
762
+ | colorLine | TypeColor | | Line color |
763
+ | colorFill | TypeColor | | Fill color |
764
+ | colorLabel | TypeColor | | Label color |
765
+ | touchDown | object | | Touch-down colors |
766
+ | states | array | | Button state definitions |
767
+
768
+ ### OBJ_FormattedText (`make: 'ftext'`)
769
+
770
+ | Property | Type | Default | Description |
771
+ |---|---|---|---|
772
+ | text | string \| string[] | | Text lines |
773
+ | modifiers | OBJ_TextModifiersDefinition | | Inline formatting modifiers |
774
+ | elements | object | | Equation elements |
775
+ | font | OBJ_Font | | Default font |
776
+ | justify | `'left'` \| `'center'` \| `'right'` | `'left'` | Line justification |
777
+ | lineSpace | number | `font.size * 0.5` | Line spacing |
778
+ | xAlign | `'left'` \| `'center'` \| `'right'` | `'left'` | Horizontal alignment |
779
+ | yAlign | `'bottom'` \| `'baseline'` \| `'middle'` \| `'top'` | `'baseline'` | Vertical alignment |
780
+ | accent | OBJ_Font | italic | Default modifier style |
781
+
782
+ ### OBJ_TextModifierDefinition (inline text modifier)
783
+
784
+ | Property | Type | Default | Description |
785
+ |---|---|---|---|
786
+ | text | string | modifierId | Replacement text (default: use the modifier key) |
787
+ | offset | TypeParsablePoint | | Text offset |
788
+ | followOffsetY | boolean | `false` | Subsequent text shares y offset |
789
+ | font | OBJ_Font | | Font changes for modified text |
790
+ | inLine | boolean | `true` | `false` to exclude from line layout |
791
+ | onClick | string \| function | | Click callback |
792
+ | touchBorder | buffer \| Point[] | `0` | Touch border |
793
+
794
+ ```js
795
+ // Formatted text with clickable modifier
796
+ figure.add({
797
+ make: 'ftext',
798
+ text: 'Click |here| to start',
799
+ modifiers: {
800
+ here: {
801
+ font: { color: [0, 0, 1, 1] },
802
+ onClick: () => console.log('clicked'),
803
+ touchBorder: 0.03,
804
+ },
805
+ },
806
+ });
807
+ ```
808
+
809
+ ---
810
+
811
+ ## 6. Equation System
812
+
813
+ Create with `make: 'equation'`. Define `elements` (terms and symbols) and `forms` (layouts).
814
+
815
+ ### EQN_Equation
816
+
817
+ | Property | Type | Default | Description |
818
+ |---|---|---|---|
819
+ | color | TypeColor | | Equation color |
820
+ | dimColor | TypeColor | | Dim color |
821
+ | font | OBJ_Font | | Math font |
822
+ | textFont | OBJ_Font | `font` | Text font |
823
+ | scale | number | `0.7` | Equation scale |
824
+ | elements | object | | Element definitions |
825
+ | forms | object | | Form definitions |
826
+ | formDefaults | object | | Default form options |
827
+ | initialForm | string | | Initial form to show |
828
+ | formSeries | object | | Named series of forms |
829
+ | defaultFormSeries | string | | Default series name |
830
+ | phrases | object | | Reusable sub-expressions |
831
+ | position | TypeParsablePoint | | Position |
832
+ | transform | Transform | | Transform |
833
+
834
+ ### Elements
835
+
836
+ Elements are defined as an object where keys are element names. String values auto-create text elements. Objects define symbols or styled text.
837
+
838
+ ```js
839
+ elements: {
840
+ v: { symbol: 'vinculum' },
841
+ equals: ' = ',
842
+ times: ' × ',
843
+ c: { color: [0, 0, 1, 1] }, // colored text 'c'
844
+ }
845
+ ```
846
+
847
+ - Undeclared strings in forms auto-create text elements
848
+ - `'_ + '` prefix creates element with display text ` + ` (underscore is stripped)
849
+ - `'b_1'`, `'b_2'` create copies that display as `b`
850
+
851
+ ### Symbols
852
+
853
+ Define in elements with `{ symbol: 'type', ...options }`. All symbols accept base properties: `color`, `isTouchable`, `touchBorder`, `onClick`, `mods`.
854
+
855
+ #### EQN_VinculumSymbol (`symbol: 'vinculum'`)
856
+
857
+ | Property | Type | Default | Description |
858
+ |---|---|---|---|
859
+ | lineWidth | number | `0.01` | Line thickness |
860
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | `'dynamic'` updates vertices on resize, `'static'` only changes scale |
861
+ | staticWidth | number \| `'first'` | `'first'` | Width for static draw mode |
862
+
863
+ #### EQN_BoxSymbol (`symbol: 'box'`)
864
+
865
+ | Property | Type | Default | Description |
866
+ |---|---|---|---|
867
+ | lineWidth | number | `0.01` | Line thickness |
868
+ | fill | boolean | `false` | Fill box instead of outline |
869
+ | width | number | | Force width |
870
+ | height | number | | Force height |
871
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
872
+
873
+ #### EQN_BracketSymbol (`symbol: 'bracket'`)
874
+
875
+ | Property | Type | Default | Description |
876
+ |---|---|---|---|
877
+ | side | `'left'` \| `'right'` \| `'top'` \| `'bottom'` | `'left'` | Bracket side |
878
+ | lineWidth | number | `0.01` | Line thickness |
879
+ | sides | number | `10` | Curve segments |
880
+ | tipWidth | number | | Tip thickness (default: 0) |
881
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
882
+
883
+ #### EQN_SquareBracketSymbol (`symbol: 'squareBracket'`)
884
+
885
+ | Property | Type | Default | Description |
886
+ |---|---|---|---|
887
+ | side | `'left'` \| `'right'` \| `'top'` \| `'bottom'` | `'left'` | Bracket side |
888
+ | lineWidth | number | `0.01` | Line thickness |
889
+ | tipWidth | number | `0.01` | Tip width |
890
+ | radius | number | `0` | Corner radius |
891
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
892
+
893
+ #### EQN_AngleBracketSymbol (`symbol: 'angleBracket'`)
894
+
895
+ | Property | Type | Default | Description |
896
+ |---|---|---|---|
897
+ | side | `'left'` \| `'right'` \| `'top'` \| `'bottom'` | `'left'` | Bracket side |
898
+ | lineWidth | number | `0.01` | Line thickness |
899
+ | width | number | | Bracket width |
900
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
901
+
902
+ #### EQN_BraceSymbol (`symbol: 'brace'`)
903
+
904
+ | Property | Type | Default | Description |
905
+ |---|---|---|---|
906
+ | side | `'left'` \| `'right'` \| `'top'` \| `'bottom'` | `'left'` | Brace side |
907
+ | lineWidth | number | `0.01` | Line thickness |
908
+ | sides | number | `10` | Curve segments |
909
+ | width | number | | Brace width |
910
+ | tipWidth | number | | Tip width |
911
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
912
+
913
+ #### EQN_BarSymbol (`symbol: 'bar'`)
914
+
915
+ | Property | Type | Default | Description |
916
+ |---|---|---|---|
917
+ | side | `'left'` \| `'right'` \| `'top'` \| `'bottom'` | `'left'` | Bar side |
918
+ | lineWidth | number | `0.01` | Line thickness |
919
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
920
+
921
+ #### EQN_RadicalSymbol (`symbol: 'radical'`)
922
+
923
+ | Property | Type | Default | Description |
924
+ |---|---|---|---|
925
+ | lineWidth | number | `0.01` | Line thickness |
926
+ | startWidth | number | `0.5` | Start stroke width ratio |
927
+ | startHeight | number | `0.5` | Start stroke height ratio |
928
+ | proportionalToHeight | boolean | `true` | Scale proportionally |
929
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
930
+
931
+ #### EQN_StrikeSymbol (`symbol: 'strike'`)
932
+
933
+ | Property | Type | Default | Description |
934
+ |---|---|---|---|
935
+ | style | `'cross'` \| `'forward'` \| `'back'` \| `'horizontal'` | `'cross'` | Strike pattern |
936
+ | lineWidth | number | `0.015` | Line thickness |
937
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
938
+
939
+ #### EQN_IntegralSymbol (`symbol: 'int'`)
940
+
941
+ | Property | Type | Default | Description |
942
+ |---|---|---|---|
943
+ | num | number | `1` | Number of integrals |
944
+ | type | `'generic'` \| `'line'` | `'generic'` | Style |
945
+ | sides | number | `30` | Curve segments |
946
+ | lineWidth | number | `0.01` | Line width |
947
+ | width | number | | Symbol width |
948
+ | serif | boolean | `true` | Show serifs |
949
+ | space | number | | Space between multiple integrals |
950
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
951
+
952
+ #### EQN_SumSymbol (`symbol: 'sum'`)
953
+
954
+ | Property | Type | Default | Description |
955
+ |---|---|---|---|
956
+ | lineWidth | number | `0.01` | Line thickness |
957
+ | sides | number | `5` | Serif segments |
958
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
959
+
960
+ #### EQN_ProdSymbol (`symbol: 'prod'`)
961
+
962
+ | Property | Type | Default | Description |
963
+ |---|---|---|---|
964
+ | lineWidth | number | `0.01` | Line thickness |
965
+ | sides | number | `5` | Serif segments |
966
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
967
+
968
+ #### EQN_ArrowSymbol (`symbol: 'arrow'`)
969
+
970
+ | Property | Type | Default | Description |
971
+ |---|---|---|---|
972
+ | direction | `'right'` \| `'left'` \| `'up'` \| `'down'` | `'right'` | Arrow direction |
973
+ | lineWidth | number | `0.01` | Line width |
974
+ | arrowWidth | number | `0.04` | Arrow head width |
975
+ | arrowLength | number | `0.04` | Arrow head length |
976
+ | draw | `'static'` \| `'dynamic'` | `'dynamic'` | Resize behavior |
977
+
978
+ #### EQN_LineSymbol (`symbol: 'line'`)
979
+
980
+ | Property | Type | Default | Description |
981
+ |---|---|---|---|
982
+ | width | number | `0.01` | Line width |
983
+ | dash | number[] | | Dash pattern |
984
+ | arrow | OBJ_LineArrows | | Arrow on end(s) |
985
+
986
+ #### EQN_DivisionSymbol (`symbol: 'division'`)
987
+
988
+ | Property | Type | Default | Description |
989
+ |---|---|---|---|
990
+ | lineWidth | number | `0.01` | Line thickness |
991
+ | radius | number | `0.03` | Dot radius |
992
+ | sides | number | `20` | Dot sides |
993
+ | space | number | `0.04` | Dot-to-line space |
994
+
995
+ ### Layout Functions
996
+
997
+ Used in form definitions. Array syntax and object syntax are both supported. Below are the full property tables for each layout function's object syntax.
998
+
999
+ #### EQN_Fraction (`frac`)
1000
+ Array: `{ frac: [numerator, symbol, denominator] }`
1001
+
1002
+ | Property | Type | Default | Description |
1003
+ |---|---|---|---|
1004
+ | numerator | TypeEquationPhrase | | Numerator content |
1005
+ | symbol | string | | Vinculum symbol name |
1006
+ | denominator | TypeEquationPhrase | | Denominator content |
1007
+ | scale | number | `1` | Content scale |
1008
+ | numeratorSpace | number | `0.05` | Space above vinculum |
1009
+ | denominatorSpace | number | `0.05` | Space below vinculum |
1010
+ | overhang | number | `0.05` | Vinculum extends beyond content |
1011
+ | offsetY | number | `0.07` | Vertical offset of fraction |
1012
+ | baseline | `'numerator'` \| `'denominator'` \| `'vinculum'` | | Baseline alignment |
1013
+ | fullContentBounds | boolean | `false` | Use full bounds |
1014
+
1015
+ #### EQN_Superscript (`sup`)
1016
+ Array: `{ sup: [content, superscript] }`
1017
+
1018
+ | Property | Type | Default | Description |
1019
+ |---|---|---|---|
1020
+ | content | TypeEquationPhrase | | Base content |
1021
+ | superscript | TypeEquationPhrase | | Superscript content |
1022
+ | scale | number | `0.5` | Superscript scale |
1023
+ | offset | TypeParsablePoint | `[0, 0]` | Superscript offset |
1024
+ | inSize | boolean | `true` | Include in phrase size |
1025
+
1026
+ #### EQN_Subscript (`sub`)
1027
+ Array: `{ sub: [content, subscript] }`
1028
+
1029
+ | Property | Type | Default | Description |
1030
+ |---|---|---|---|
1031
+ | content | TypeEquationPhrase | | Base content |
1032
+ | subscript | TypeEquationPhrase | | Subscript content |
1033
+ | scale | number | `0.5` | Subscript scale |
1034
+ | offset | TypeParsablePoint | `[0, 0]` | Subscript offset |
1035
+ | inSize | boolean | `true` | Include in phrase size |
1036
+
1037
+ #### EQN_SuperscriptSubscript (`supSub`)
1038
+ Array: `{ supSub: [content, superscript, subscript] }`
1039
+
1040
+ | Property | Type | Default | Description |
1041
+ |---|---|---|---|
1042
+ | content | TypeEquationPhrase | | Base content |
1043
+ | superscript | TypeEquationPhrase | | Superscript content |
1044
+ | subscript | TypeEquationPhrase | | Subscript content |
1045
+ | scale | number | `0.5` | Script scale |
1046
+ | superscriptOffset | TypeParsablePoint | `[0, 0]` | Superscript offset |
1047
+ | subscriptOffset | TypeParsablePoint | `[0, 0]` | Subscript offset |
1048
+ | inSize | boolean | `true` | Include in phrase size |
1049
+
1050
+ #### EQN_Bracket (`brac`)
1051
+ Array: `{ brac: [left_symbol, content, right_symbol] }`
1052
+
1053
+ | Property | Type | Default | Description |
1054
+ |---|---|---|---|
1055
+ | left | string | | Left bracket symbol |
1056
+ | content | TypeEquationPhrase | | Content |
1057
+ | right | string | | Right bracket symbol |
1058
+ | inSize | boolean | `true` | Include brackets in size |
1059
+ | insideSpace | number | `0.03` | Space between brackets and content |
1060
+ | outsideSpace | number | `0.03` | Space between brackets and neighbors |
1061
+ | topSpace | number | `0.05` | Bracket extends above content |
1062
+ | bottomSpace | number | `0.05` | Bracket extends below content |
1063
+ | minContentHeight | number \| null | `null` | Min content height for bracket sizing |
1064
+ | minContentDescent | number \| null | `null` | Min content descent |
1065
+ | height | number \| null | `null` | Force bracket height |
1066
+ | descent | number \| null | `null` | Force bracket descent |
1067
+ | fullContentBounds | boolean | `false` | Use full bounds |
1068
+ | useFullBounds | boolean | `false` | Resulting phrase uses full bounds |
1069
+
1070
+ #### EQN_Root (`root`)
1071
+ Array: `{ root: [radical_symbol, content] }`
1072
+
1073
+ | Property | Type | Default | Description |
1074
+ |---|---|---|---|
1075
+ | symbol | string | | Radical symbol name |
1076
+ | content | TypeEquationPhrase | | Content under radical |
1077
+ | inSize | boolean | `true` | Include radical in size |
1078
+ | space | number | `0.02` | Default space (all directions) |
1079
+ | topSpace | number | `space` | Space above content |
1080
+ | rightSpace | number | `space` | Radical overhang right |
1081
+ | bottomSpace | number | `space` | Radical descent below content |
1082
+ | leftSpace | number | `space` | Space left of content |
1083
+ | root | TypeEquationPhrase | | Index content (e.g., '3' for cube root) |
1084
+ | rootOffset | TypeParsablePoint | `[0, 0.06]` | Index offset |
1085
+ | rootScale | number | `0.6` | Index scale |
1086
+ | fullContentBounds | boolean | `false` | Use full bounds |
1087
+ | useFullBounds | boolean | `false` | Resulting phrase uses full bounds |
1088
+
1089
+ #### EQN_Strike (`strike`)
1090
+ Array: `{ strike: [content, strike_symbol] }`
1091
+
1092
+ | Property | Type | Default | Description |
1093
+ |---|---|---|---|
1094
+ | content | TypeEquationPhrase | | Content to strike through |
1095
+ | symbol | string | | Strike symbol name |
1096
+ | inSize | boolean | `false` | Include strike in size |
1097
+ | space | number | `0.02` | Overhang (all directions) |
1098
+ | topSpace | number | `space` | Top overhang |
1099
+ | rightSpace | number | `space` | Right overhang |
1100
+ | bottomSpace | number | `space` | Bottom overhang |
1101
+ | leftSpace | number | `space` | Left overhang |
1102
+ | fullContentBounds | boolean | `false` | Use full bounds |
1103
+ | useFullBounds | boolean | `false` | Resulting phrase uses full bounds |
1104
+
1105
+ #### EQN_StrikeComment (`topStrike` / `bottomStrike`)
1106
+ Array: `{ topStrike: [content, symbol, comment] }`
1107
+
1108
+ | Property | Type | Default | Description |
1109
+ |---|---|---|---|
1110
+ | content | TypeEquationPhrase | | Content to strike |
1111
+ | symbol | string | | Strike symbol |
1112
+ | comment | TypeEquationPhrase | | Comment text |
1113
+ | inSize | boolean | `true` | Include in size |
1114
+ | space | number | `0.03` | Strike overhang |
1115
+ | scale | number | `0.6` | Comment scale |
1116
+ | commentSpace | number | `0.03` | Space between strike and comment |
1117
+
1118
+ #### EQN_Box (`box`)
1119
+ Array: `{ box: [content, symbol, inSize, space] }`
1120
+
1121
+ | Property | Type | Default | Description |
1122
+ |---|---|---|---|
1123
+ | content | TypeEquationPhrase | | Content |
1124
+ | symbol | string | | Box symbol |
1125
+ | inSize | boolean | `false` | Include box in size |
1126
+ | space | number | `0` | Space between box and content |
1127
+ | topSpace | number | `space` | Top space |
1128
+ | rightSpace | number | `space` | Right space |
1129
+ | bottomSpace | number | `space` | Bottom space |
1130
+ | leftSpace | number | `space` | Left space |
1131
+ | fullContentBounds | boolean | `false` | Use full bounds |
1132
+ | useFullBounds | boolean | `false` | Resulting phrase uses full bounds |
1133
+
1134
+ #### EQN_TouchBox (`tBox`)
1135
+ Array: `{ tBox: [content, symbol, space] }`
1136
+
1137
+ | Property | Type | Default | Description |
1138
+ |---|---|---|---|
1139
+ | content | TypeEquationPhrase | | Content |
1140
+ | symbol | string | | Box symbol |
1141
+ | space | number | `0` | Space between box and content |
1142
+ | topSpace | number | `space` | Top space |
1143
+ | rightSpace | number | `space` | Right space |
1144
+ | bottomSpace | number | `space` | Bottom space |
1145
+ | leftSpace | number | `space` | Left space |
1146
+
1147
+ #### EQN_Bar (`bar` / `topBar` / `bottomBar`)
1148
+ Array: `{ bar: [content, symbol, side] }` or `{ topBar: [content, symbol] }`
1149
+
1150
+ | Property | Type | Default | Description |
1151
+ |---|---|---|---|
1152
+ | content | TypeEquationPhrase | | Content |
1153
+ | symbol | string | | Bar/bracket/brace symbol |
1154
+ | inSize | boolean | `true` | Include in size |
1155
+ | space | number | `0.03` | Space between symbol and content |
1156
+ | overhang | number | `0` | Symbol extends beyond content |
1157
+ | length | number | | Total symbol length (overrides overhang) |
1158
+ | left | number | | Left extension (top/bottom only) |
1159
+ | right | number | | Right extension (top/bottom only) |
1160
+ | top | number | | Top extension (left/right only) |
1161
+ | bottom | number | | Bottom extension (left/right only) |
1162
+ | side | `'left'` \| `'right'` \| `'top'` \| `'bottom'` | `'top'` | Symbol position |
1163
+ | minContentHeight | number | | Min content height for sizing |
1164
+ | minContentDescent | number | | Min content descent |
1165
+ | minContentAscent | number | | Min content ascent |
1166
+ | descent | number | | Force descent |
1167
+ | fullContentBounds | boolean | `false` | Use full bounds |
1168
+ | useFullBounds | boolean | `false` | Resulting phrase uses full bounds |
1169
+
1170
+ #### EQN_Comment (`topComment` / `bottomComment`)
1171
+ Array: `{ topComment: [content, comment, symbol] }`
1172
+
1173
+ | Property | Type | Default | Description |
1174
+ |---|---|---|---|
1175
+ | content | TypeEquationPhrase | | Main content |
1176
+ | comment | TypeEquationPhrase | | Comment content |
1177
+ | symbol | string | | Symbol between content and comment |
1178
+ | contentSpace | number | `0.03` | Space from content to symbol |
1179
+ | commentSpace | number | `0.03` | Space from symbol to comment |
1180
+ | contentLineSpace | number | `0.03` | Space from line symbol to content |
1181
+ | commentLineSpace | number | `0.03` | Space from line symbol to comment |
1182
+ | scale | number | `0.6` | Comment scale |
1183
+ | inSize | boolean | `true` | Include in size |
1184
+ | fullContentBounds | boolean | `false` | Use full bounds |
1185
+ | useFullBounds | boolean | `false` | Resulting phrase uses full bounds |
1186
+
1187
+ #### EQN_Integral (`int`)
1188
+ Array: `{ int: [symbol, content, from, to] }`
1189
+
1190
+ | Property | Type | Default | Description |
1191
+ |---|---|---|---|
1192
+ | symbol | string | | Integral symbol |
1193
+ | content | TypeEquationPhrase | | Integrand |
1194
+ | from | TypeEquationPhrase | | Lower limit |
1195
+ | to | TypeEquationPhrase | | Upper limit |
1196
+ | inSize | boolean | `true` | Include in size |
1197
+ | space | number | `0.05` | Space between symbol and content |
1198
+ | topSpace | number | `0.1` | Symbol extends above content |
1199
+ | bottomSpace | number | `0.1` | Symbol extends below content |
1200
+ | height | number | | Force symbol height |
1201
+ | yOffset | number | `0` | Symbol y offset |
1202
+ | scale | number | `1` | Content scale |
1203
+ | fromScale | number | `0.5` | Lower limit scale |
1204
+ | toScale | number | `0.5` | Upper limit scale |
1205
+ | fromOffset | TypeParsablePoint | `[0, 0]` | Lower limit offset |
1206
+ | toOffset | TypeParsablePoint | `[0, 0]` | Upper limit offset |
1207
+ | limitsPosition | `'side'` \| `'topBottom'` \| `'topBottomCenter'` | `'side'` | Limits placement |
1208
+ | limitsAroundContent | boolean | | `false` aligns content left of limits |
1209
+
1210
+ #### EQN_SumOf (`sumOf`)
1211
+ Array: `{ sumOf: [symbol, content, from, to] }`
1212
+
1213
+ | Property | Type | Default | Description |
1214
+ |---|---|---|---|
1215
+ | symbol | string | | Sum symbol |
1216
+ | content | TypeEquationPhrase | | Content |
1217
+ | from | TypeEquationPhrase | | Lower limit |
1218
+ | to | TypeEquationPhrase | | Upper limit |
1219
+ | inSize | boolean | `true` | Include in size |
1220
+ | space | number | `0.1` | Space between symbol and content |
1221
+ | topSpace | number | `0.07` | Symbol extends above content |
1222
+ | bottomSpace | number | `0.07` | Symbol extends below content |
1223
+ | height | number | | Force symbol height |
1224
+ | yOffset | number | `0` | Symbol y offset |
1225
+ | scale | number | `1` | Content scale |
1226
+ | fromScale | number | `0.5` | Lower limit scale |
1227
+ | toScale | number | `0.5` | Upper limit scale |
1228
+ | fromSpace | number | `0.04` | Space between symbol and from |
1229
+ | toSpace | number | `0.04` | Space between symbol and to |
1230
+ | fromOffset | TypeParsablePoint | `[0, 0]` | Lower limit offset |
1231
+ | toOffset | TypeParsablePoint | `[0, 0]` | Upper limit offset |
1232
+ | fullContentBounds | boolean | `false` | Use full bounds |
1233
+
1234
+ #### EQN_ProdOf (`prodOf`)
1235
+ Same properties as EQN_SumOf. Uses `prod` symbol instead.
1236
+
1237
+ #### EQN_Scale (`scale`)
1238
+ Array: `{ scale: [content, scale_factor] }`
1239
+
1240
+ | Property | Type | Default | Description |
1241
+ |---|---|---|---|
1242
+ | content | TypeEquationPhrase | | Content |
1243
+ | scale | number | `1` | Scale factor |
1244
+ | fullContentBounds | boolean | `false` | Use full bounds |
1245
+
1246
+ #### EQN_Color (`color`)
1247
+ Array: `{ color: [content, [r, g, b, a]] }`
1248
+
1249
+ | Property | Type | Default | Description |
1250
+ |---|---|---|---|
1251
+ | content | TypeEquationPhrase | | Content |
1252
+ | color | TypeColor | | Color to apply |
1253
+ | fullContentBounds | boolean | `false` | Use full bounds |
1254
+
1255
+ #### EQN_Container (`container`)
1256
+ Array: `{ container: [content, width] }`
1257
+
1258
+ | Property | Type | Default | Description |
1259
+ |---|---|---|---|
1260
+ | content | TypeEquationPhrase | | Content |
1261
+ | width | number \| null | `null` | Fixed width |
1262
+ | inSize | boolean | `true` | Include in size |
1263
+ | descent | number \| null | `null` | Fixed descent |
1264
+ | ascent | number \| null | `null` | Fixed ascent |
1265
+ | xAlign | `'left'` \| `'center'` \| `'right'` \| number | `'center'` | Content x alignment |
1266
+ | yAlign | `'bottom'` \| `'middle'` \| `'top'` \| `'baseline'` \| number | `'baseline'` | Content y alignment |
1267
+ | fit | `'width'` \| `'height'` \| `'contain'` \| null | `null` | Auto-fit mode |
1268
+ | scale | number | `1` | Content scale |
1269
+ | fullContentBounds | boolean | `false` | Use full bounds |
1270
+ | showContent | boolean | `true` | `false` creates invisible container |
1271
+
1272
+ #### EQN_Offset (`offset`)
1273
+ Array: `{ offset: [content, [x, y]] }`
1274
+
1275
+ | Property | Type | Default | Description |
1276
+ |---|---|---|---|
1277
+ | content | TypeEquationPhrase | | Content |
1278
+ | offset | TypeParsablePoint | `[0, 0]` | Offset amount |
1279
+ | inSize | boolean | `true` | Include in size |
1280
+ | fullContentBounds | boolean | `false` | Use full bounds |
1281
+
1282
+ #### EQN_Pad (`pad`)
1283
+ Array: `{ pad: [content, top, right, bottom, left] }`
1284
+
1285
+ | Property | Type | Default | Description |
1286
+ |---|---|---|---|
1287
+ | content | TypeEquationPhrase | | Content |
1288
+ | top | number | `0` | Top padding |
1289
+ | right | number | `0` | Right padding |
1290
+ | bottom | number | `0` | Bottom padding |
1291
+ | left | number | `0` | Left padding |
1292
+
1293
+ #### EQN_Matrix (`matrix`)
1294
+ Array: `{ matrix: [order, left, content, right] }`
1295
+
1296
+ | Property | Type | Default | Description |
1297
+ |---|---|---|---|
1298
+ | order | [rows, cols] | `[1, content.length]` | Matrix dimensions |
1299
+ | left | string | | Left bracket symbol |
1300
+ | content | TypeEquationPhrase[] | | Matrix elements (flat array) |
1301
+ | right | string | | Right bracket symbol |
1302
+ | scale | number | `0.7` | Element scale |
1303
+ | fit | `'max'` \| `'min'` \| TypeParsablePoint | `'min'` | Cell sizing mode |
1304
+ | space | TypeParsablePoint | `[0.05, 0.05]` | Cell spacing |
1305
+ | yAlign | `'baseline'` \| `'middle'` | `'baseline'` | Row alignment |
1306
+ | brac | EQN_Bracket | | Bracket options |
1307
+ | fullContentBounds | boolean | `false` | Use full bounds |
1308
+
1309
+ #### EQN_Lines (`lines`)
1310
+
1311
+ | Property | Type | Default | Description |
1312
+ |---|---|---|---|
1313
+ | content | (TypeEquationPhrase \| EQN_Line)[] | | Array of lines |
1314
+ | justify | `'left'` \| `'center'` \| `'right'` \| `'element'` | `'left'` | Line alignment |
1315
+ | baselineSpace | number \| null | `null` | Space between baselines (overrides space) |
1316
+ | space | number | `0` | Space between descent and ascent |
1317
+ | yAlign | `'bottom'` \| `'middle'` \| `'top'` \| number | `0` | Vertical alignment (number = line index baseline) |
1318
+ | fullContentBounds | boolean | `false` | Use full bounds |
1319
+
1320
+ EQN_Line (individual line in `lines`):
1321
+
1322
+ | Property | Type | Default | Description |
1323
+ |---|---|---|---|
1324
+ | content | TypeEquationPhrase | | Line content |
1325
+ | justify | string | | Element name for `'element'` justify mode |
1326
+ | space | number | `0` | Override spacing for this line |
1327
+ | baselineSpace | number \| null | `null` | Override baseline space for this line |
1328
+ | offset | number | `0` | X offset from justification position |
1329
+
1330
+ #### EQN_Annotate (`annotate`)
1331
+ Object only (no array syntax).
1332
+
1333
+ | Property | Type | Default | Description |
1334
+ |---|---|---|---|
1335
+ | content | TypeEquationPhrase | | Main content |
1336
+ | annotation | EQN_Annotation | | Single annotation |
1337
+ | annotations | EQN_Annotation[] | | Multiple annotations |
1338
+ | glyphs | EQN_Glyphs | | Glyph annotations (encompass, top, bottom, left, right, line) |
1339
+ | inSize | boolean | `true` | Include annotations in size |
1340
+ | space | number | `0` | Extend size on all sides |
1341
+ | topSpace | number | | Top size extension |
1342
+ | bottomSpace | number | | Bottom size extension |
1343
+ | leftSpace | number | | Left size extension |
1344
+ | rightSpace | number | | Right size extension |
1345
+ | contentScale | number | `1` | Content scale |
1346
+ | fullContentBounds | boolean | `false` | Use full bounds |
1347
+ | useFullBounds | boolean | `false` | Resulting phrase uses full bounds |
1348
+
1349
+ EQN_Annotation (annotation positioning):
1350
+
1351
+ | Property | Type | Default | Description |
1352
+ |---|---|---|---|
1353
+ | content | TypeEquationPhrase | | Annotation content |
1354
+ | xPosition | `'left'` \| `'center'` \| `'right'` \| number | `'center'` | Position relative to content |
1355
+ | yPosition | `'bottom'` \| `'baseline'` \| `'middle'` \| `'top'` \| number | `'top'` | Position relative to content |
1356
+ | xAlign | `'left'` \| `'center'` \| `'right'` \| number | `'center'` | Annotation alignment |
1357
+ | yAlign | `'bottom'` \| `'baseline'` \| `'middle'` \| `'top'` \| number | `'bottom'` | Annotation alignment |
1358
+ | offset | TypeParsablePoint | `[0, 0]` | Annotation offset |
1359
+ | scale | number | `1` | Annotation scale |
1360
+ | inSize | boolean | `true` | Include in phrase size |
1361
+ | fullContentBounds | boolean | `false` | Use full bounds |
1362
+
1363
+ EQN_Glyphs (glyph positions in `annotate`):
1364
+
1365
+ | Property | Type | Description |
1366
+ |---|---|---|
1367
+ | encompass | EQN_EncompassGlyph | Glyph surrounding content |
1368
+ | top | EQN_TopBottomGlyph | Glyph above content |
1369
+ | bottom | EQN_TopBottomGlyph | Glyph below content |
1370
+ | left | EQN_LeftRightGlyph | Glyph left of content |
1371
+ | right | EQN_LeftRightGlyph | Glyph right of content |
1372
+ | line | EQN_LineGlyph | Line glyph connecting content to annotation |
1373
+
1374
+ ### Annotate Glyph Sub-types
1375
+
1376
+ #### EQN_EncompassGlyph (surrounds content)
1377
+
1378
+ | Property | Type | Default | Description |
1379
+ |---|---|---|---|
1380
+ | symbol | string | | Glyph symbol name |
1381
+ | annotation | EQN_Annotation | | Single annotation |
1382
+ | annotations | EQN_Annotation[] | | Multiple annotations |
1383
+ | space | number | `0` | Space glyph extends beyond content (all sides) |
1384
+ | topSpace | number | | Override top space |
1385
+ | rightSpace | number | | Override right space |
1386
+ | bottomSpace | number | | Override bottom space |
1387
+ | leftSpace | number | | Override left space |
1388
+
1389
+ #### EQN_LeftRightGlyph (left or right of content)
1390
+
1391
+ | Property | Type | Default | Description |
1392
+ |---|---|---|---|
1393
+ | symbol | string | | Glyph symbol name |
1394
+ | annotation | EQN_Annotation | | Single annotation |
1395
+ | annotations | EQN_Annotation[] | | Multiple annotations |
1396
+ | space | number | `0` | Horizontal space between glyph and content |
1397
+ | overhang | number | `0` | Glyph extends above/below content |
1398
+ | topSpace | number | | Override top overhang |
1399
+ | bottomSpace | number | | Override bottom overhang |
1400
+ | minContentHeight | number | | Min content height for auto sizing |
1401
+ | minContentDescent | number | | Min content descent for auto sizing |
1402
+ | minContentAscent | number | | Min content ascent for auto sizing |
1403
+ | descent | number | | Force glyph descent |
1404
+ | height | number | | Force glyph height |
1405
+ | yOffset | number | `0` | Y offset of glyph |
1406
+ | annotationsOverContent | boolean | `false` | Only glyph (not annotations) separated by space |
1407
+
1408
+ #### EQN_TopBottomGlyph (above or below content)
1409
+
1410
+ | Property | Type | Default | Description |
1411
+ |---|---|---|---|
1412
+ | symbol | string | | Glyph symbol name |
1413
+ | annotation | EQN_Annotation | | Single annotation |
1414
+ | annotations | EQN_Annotation[] | | Multiple annotations |
1415
+ | space | number | `0` | Vertical space between glyph and content |
1416
+ | overhang | number | `0` | Glyph extends left/right of content |
1417
+ | width | number | | Force glyph width |
1418
+ | leftSpace | number | | Glyph extends beyond content left |
1419
+ | rightSpace | number | | Glyph extends beyond content right |
1420
+ | xOffset | number | `0` | X offset of glyph |
1421
+ | annotationsOverContent | boolean | `false` | Only glyph (not annotations) separated by space |
1422
+
1423
+ #### EQN_LineGlyph (line connecting content to annotation)
1424
+
1425
+ | Property | Type | Default | Description |
1426
+ |---|---|---|---|
1427
+ | symbol | string | | Line symbol name |
1428
+ | content | object | | Content endpoint alignment: `{ xAlign, yAlign, space }` |
1429
+ | annotation | object | | Annotation endpoint alignment: `{ xAlign, yAlign, space }` |
1430
+ | annotationIndex | number | | Which annotation to draw line to |
1431
+
1432
+ ### Form Object Options
1433
+
1434
+ When defining a form as an object (instead of just a content array), additional options control alignment and animation:
1435
+
1436
+ ```js
1437
+ forms: {
1438
+ formName: {
1439
+ content: [...], // Equation phrase
1440
+ alignment: {
1441
+ fixTo: 'elementName', // Fix alignment to this element
1442
+ xAlign: 'center', // 'left'|'center'|'right'|number
1443
+ yAlign: 'baseline', // 'bottom'|'baseline'|'middle'|'top'|number
1444
+ },
1445
+ translation: { // Per-element animation paths
1446
+ elementName: {
1447
+ style: 'curved', // 'linear'|'curved'
1448
+ direction: 'up', // 'up'|'down'|'left'|'right'
1449
+ mag: 0.5, // Curve magnitude
1450
+ },
1451
+ },
1452
+ scale: { elementName: 1.5 }, // Per-element target scales during animation
1453
+ fromPrev: { elementName: 'otherElement' }, // Animate from another element's position
1454
+ fromNext: { elementName: 'otherElement' }, // Animate from another element's position (reverse)
1455
+ },
1456
+ }
1457
+ ```
1458
+
1459
+ ### Forms and Animation
1460
+
1461
+ ```js
1462
+ const eqn = figure.add({
1463
+ make: 'equation',
1464
+ elements: { v: { symbol: 'vinculum' }, equals: ' = ', times: ' × ' },
1465
+ formDefaults: { alignment: { fixTo: 'equals' } },
1466
+ forms: {
1467
+ 1: ['a', 'equals', { frac: ['b', 'v', 'c'] }],
1468
+ 2: {
1469
+ content: ['c', 'times', 'a', 'equals', 'b'],
1470
+ translation: { c: { style: 'curved', direction: 'down', mag: 0.5 } },
1471
+ },
1472
+ },
1473
+ formSeries: { default: ['1', '2'] },
1474
+ });
1475
+
1476
+ eqn.showForm('1');
1477
+ eqn.goToForm({ form: '2', delay: 1, duration: 1.5, animate: 'move' });
1478
+ eqn.nextForm(); // Navigate formSeries
1479
+ eqn.prevForm();
1480
+ ```
1481
+
1482
+ ### Phrases (reusable sub-expressions)
1483
+ ```js
1484
+ {
1485
+ make: 'equation',
1486
+ phrases: { Csq: { sup: ['C', '2'] } },
1487
+ forms: { 1: ['a', '_ = ', 'Csq'] },
1488
+ }
1489
+ ```
1490
+
1491
+ ---
1492
+
1493
+ ## 7. Animation System
1494
+
1495
+ Animations use a builder chain on `element.animations`.
1496
+
1497
+ ### AnimationBuilder Methods
1498
+
1499
+ ```js
1500
+ element.animations.new()
1501
+ .position(target_or_options)
1502
+ .rotation(target_or_options)
1503
+ .scale(target_or_options)
1504
+ .transform(target_or_options)
1505
+ .color(target_or_options)
1506
+ .opacity(target_or_options)
1507
+ .dissolveIn(duration?)
1508
+ .dissolveOut(duration?)
1509
+ .dim(options?)
1510
+ .undim(options?)
1511
+ .pulse(scale_or_options)
1512
+ .scenario(options)
1513
+ .scenarios(options)
1514
+ .delay(duration)
1515
+ .trigger(callback_or_options)
1516
+ .custom(callback_or_options)
1517
+ .inParallel([steps])
1518
+ .whenFinished(callback)
1519
+ .start();
1520
+ ```
1521
+
1522
+ Steps are sequential by default. Use `.inParallel([...])` for simultaneous.
1523
+
1524
+ ### OBJ_AnimationStep (base)
1525
+
1526
+ | Property | Type | Default | Description |
1527
+ |---|---|---|---|
1528
+ | duration | number | `0` | Duration in seconds |
1529
+ | delay | number | `0` | Delay before start |
1530
+ | name | string | random | Animation name |
1531
+ | onFinish | function \| null | null | Completion callback |
1532
+ | completeOnCancel | boolean \| null | null | Skip to end on cancel |
1533
+ | precision | number | `8` | Calculation precision |
1534
+
1535
+ ### OBJ_ElementAnimationStep (extends OBJ_AnimationStep)
1536
+
1537
+ | Property | Type | Default | Description |
1538
+ |---|---|---|---|
1539
+ | element | FigureElement | | Target element |
1540
+ | progression | string \| function | `'easeinout'` | `'linear'`, `'easeinout'`, `'easein'`, `'easeout'`, or custom function |
1541
+
1542
+ ### OBJ_PositionAnimationStep
1543
+
1544
+ | Property | Type | Default | Description |
1545
+ |---|---|---|---|
1546
+ | start | Point | current | Start position |
1547
+ | target | Point | | Target position |
1548
+ | delta | Point | | Delta (if no target) |
1549
+ | velocity | Point \| null | null | Velocity (overrides duration) |
1550
+ | path | OBJ_TranslationPath | `{ style: 'linear' }` | Path style |
1551
+ | maxDuration | number \| null | null | Duration cap |
1552
+
1553
+ ### OBJ_RotationAnimationStep
1554
+
1555
+ | Property | Type | Default | Description |
1556
+ |---|---|---|---|
1557
+ | start | number | current | Start angle |
1558
+ | target | number | | Target angle |
1559
+ | delta | number | | Delta (if no target) |
1560
+ | velocity | number \| null | null | Velocity (rad/s) |
1561
+ | direction | `0` \| `1` \| `-1` \| `2` | `0` | 0=quickest, 1=CCW, -1=CW, 2=avoid 0 |
1562
+
1563
+ ### OBJ_ScaleAnimationStep
1564
+
1565
+ | Property | Type | Default | Description |
1566
+ |---|---|---|---|
1567
+ | start | Point \| number | current | Start scale |
1568
+ | target | Point \| number | | Target scale |
1569
+ | delta | Point \| number | | Delta (if no target) |
1570
+
1571
+ ### OBJ_TransformAnimationStep
1572
+
1573
+ | Property | Type | Default | Description |
1574
+ |---|---|---|---|
1575
+ | start | Transform | current | Start transform |
1576
+ | target | Transform | | Target transform |
1577
+ | path | OBJ_TranslationPath | `{ style: 'linear' }` | Translation path |
1578
+ | rotDirection | `0` \| `1` \| `-1` \| `2` | `0` | Rotation direction |
1579
+ | clipRotationTo | `'0to360'` \| `'-180to180'` \| null | null | Clip rotation |
1580
+
1581
+ ### OBJ_ColorAnimationStep
1582
+
1583
+ | Property | Type | Default | Description |
1584
+ |---|---|---|---|
1585
+ | start | TypeColor | current | Start color |
1586
+ | target | TypeColor \| `'dim'` \| `'undim'` | | Target color |
1587
+ | dissolve | `'in'` \| `'out'` \| null | null | Dissolve mode |
1588
+
1589
+ ### OBJ_OpacityAnimationStep
1590
+
1591
+ | Property | Type | Default | Description |
1592
+ |---|---|---|---|
1593
+ | start | number | current | Start opacity |
1594
+ | target | number | | Target opacity |
1595
+ | dissolve | `'in'` \| `'out'` \| null | null | Dissolve mode |
1596
+
1597
+ ### OBJ_CustomAnimationStep
1598
+
1599
+ | Property | Type | Default | Description |
1600
+ |---|---|---|---|
1601
+ | callback | function | | Called each frame with percent (0-1) |
1602
+ | duration | number \| null | | null = infinite |
1603
+ | progression | string \| function | `'linear'` | Progression function |
1604
+
1605
+ ### OBJ_TriggerAnimationStep
1606
+
1607
+ | Property | Type | Default | Description |
1608
+ |---|---|---|---|
1609
+ | callback | function | | Function to execute |
1610
+ | payload | any | null | Argument to callback |
1611
+
1612
+ ### OBJ_ScenarioAnimationStep
1613
+
1614
+ | Property | Type | Default | Description |
1615
+ |---|---|---|---|
1616
+ | start | string \| OBJ_Scenario | current | Start scenario |
1617
+ | target | string \| OBJ_Scenario | | Target scenario |
1618
+ | velocity | OBJ_ScenarioVelocity | null | Velocity (overrides duration) |
1619
+ | path | OBJ_TranslationPath | `{ style: 'linear' }` | Translation path |
1620
+ | rotDirection | `0` \| `1` \| `-1` \| `2` | `0` | Rotation direction |
1621
+
1622
+ ### OBJ_PulseAnimationStep
1623
+
1624
+ | Property | Type | Default | Description |
1625
+ |---|---|---|---|
1626
+ | scale | number | `1.5` | Max pulse scale |
1627
+ | frequency | number | `0` | Hz (0 = one cycle) |
1628
+ | rotation | number | | Max rotation |
1629
+ | centerOn | FigureElement \| Point \| null | element center | Pulse center |
1630
+ | xAlign | `'left'` \| `'center'` \| `'right'` \| `'origin'` | `'center'` | X alignment |
1631
+ | yAlign | `'bottom'` \| `'middle'` \| `'top'` \| `'origin'` | `'center'` | Y alignment |
1632
+ | num | number | `1` | Draw copies |
1633
+
1634
+ ### OBJ_TranslationPath
1635
+
1636
+ | Property | Type | Default | Description |
1637
+ |---|---|---|---|
1638
+ | style | `'linear'` \| `'curve'` | `'linear'` | Path style |
1639
+ | magnitude | number | | Curve magnitude |
1640
+ | offset | number | | Point on line (0.5=mid) |
1641
+ | angle | number | `π/2` | Control point angle |
1642
+ | direction | `'positive'` \| `'negative'` \| `'up'` \| `'down'` \| `'left'` \| `'right'` | | Curve side |
1643
+ | controlPoint | Point \| null | null | Direct bezier control point |
1644
+
1645
+ ```js
1646
+ // Animation examples
1647
+ const sq = figure.add({ make: 'rectangle', width: 0.4, height: 0.4, color: [1, 0, 0, 1] });
1648
+
1649
+ // Sequential chain
1650
+ sq.animations.new()
1651
+ .position({ target: [0.5, 0], duration: 1 })
1652
+ .rotation({ target: Math.PI / 4, duration: 0.5 })
1653
+ .dissolveOut(0.5)
1654
+ .start();
1655
+
1656
+ // Parallel
1657
+ sq.animations.new()
1658
+ .inParallel([
1659
+ sq.animations.position({ target: [0.5, 0], duration: 1 }),
1660
+ sq.animations.rotation({ target: Math.PI, duration: 1 }),
1661
+ ])
1662
+ .start();
1663
+
1664
+ // Curved path
1665
+ sq.animations.new()
1666
+ .position({ target: [0.5, 0.5], duration: 2, path: { style: 'curve', direction: 'up', magnitude: 0.5 } })
1667
+ .start();
1668
+ ```
1669
+
1670
+ ---
1671
+
1672
+ ## 8. Interactivity
1673
+
1674
+ ### OBJ_Touch
1675
+
1676
+ | Property | Type | Default | Description |
1677
+ |---|---|---|---|
1678
+ | enable | boolean | `true` | Enable touch |
1679
+ | onClick | function \| string | | Touch callback |
1680
+
1681
+ ### OBJ_ElementMove
1682
+
1683
+ | Property | Type | Default | Description |
1684
+ |---|---|---|---|
1685
+ | type | `'translation'` \| `'rotation'` \| `'position'` \| `'scale'` \| `'scaleX'` \| `'scaleY'` \| `'scaleZ'` | `'translation'` | Move type |
1686
+ | bounds | TypeParsableBounds | | Movement bounds rectangle |
1687
+ | plane | Plane | | Movement plane (3D) |
1688
+ | maxVelocity | number \| TypeParsablePoint | `5` | Maximum velocity |
1689
+ | freely | OBJ_ElementMoveFreely \| false | | Free movement with deceleration (false to disable) |
1690
+ | element | FigureElement \| string \| null | | Element to move (default: self) |
1691
+
1692
+ ### OBJ_ElementMoveFreely
1693
+
1694
+ | Property | Type | Default | Description |
1695
+ |---|---|---|---|
1696
+ | deceleration | number | | Deceleration (local units/s²) |
1697
+ | bounceLoss | number | | Velocity loss on bounce (0.5 = 50% loss) |
1698
+ | zeroVelocityThreshold | number | | Threshold to stop movement |
1699
+ | callback | function \| string \| null | | Called each frame of free movement |
1700
+
1701
+ ### OBJ_Pulse
1702
+
1703
+ | Property | Type | Default | Description |
1704
+ |---|---|---|---|
1705
+ | scale | number | `1.5` | Max pulse scale |
1706
+ | duration | number | `1` | Duration in seconds |
1707
+ | frequency | number | `0` | Hz (0 = one cycle) |
1708
+ | rotation | number | | Max rotation angle |
1709
+ | xAlign | `'left'` \| `'center'` \| `'right'` \| `'origin'` | `'center'` | X alignment |
1710
+ | yAlign | `'bottom'` \| `'middle'` \| `'top'` \| `'origin'` | `'middle'` | Y alignment |
1711
+ | space | `'figure'` \| `'gl'` \| `'local'` \| `'draw'` | `'figure'` | Coordinate space |
1712
+ | num | number | `1` | Number of copies to draw |
1713
+ | when | TypeWhen | `'syncNow'` | When to start pulse |
1714
+ | done | function \| string \| null | `null` | Completion callback |
1715
+ | progression | string \| function | `'sinusoid'` | Progress function |
1716
+
1717
+ ### Making elements interactive
1718
+
1719
+ ```js
1720
+ // Movable with bounds
1721
+ figure.add({
1722
+ make: 'polygon', sides: 100, radius: 0.2,
1723
+ move: {
1724
+ type: 'translation',
1725
+ bounds: { left: -0.8, bottom: -0.8, right: 0.8, top: 0.8 },
1726
+ freely: { deceleration: 0.5 },
1727
+ },
1728
+ });
1729
+
1730
+ // Rotatable
1731
+ figure.add({
1732
+ make: 'polygon', sides: 6, radius: 0.3,
1733
+ move: { type: 'rotation' },
1734
+ });
1735
+
1736
+ // Touchable with callback
1737
+ element.setTouchable();
1738
+ element.onClick = () => { /* handle */ };
1739
+
1740
+ // Or via mods
1741
+ figure.add({
1742
+ make: 'polygon', radius: 0.3, sides: 4,
1743
+ mods: { isTouchable: true, touchBorder: 0.1 },
1744
+ });
1745
+ ```
1746
+
1747
+ ### Notifications
1748
+
1749
+ ```js
1750
+ element.notifications.add('setTransform', () => {
1751
+ const p = element.getPosition().round(1);
1752
+ text.setText({ text: `(${p.x.toFixed(1)}, ${p.y.toFixed(1)})` });
1753
+ });
1754
+ element.notifications.add('onClick', () => { /* ... */ });
1755
+ // Common: 'setTransform', 'onClick', 'animationsFinished', 'setFigure'
1756
+ ```
1757
+
1758
+ ---
1759
+
1760
+ ## 9. Scenarios
1761
+
1762
+ Named presets for position, rotation, scale, and color.
1763
+
1764
+ ### OBJ_Scenario
1765
+
1766
+ | Property | Type | Description |
1767
+ |---|---|---|
1768
+ | position | TypeParsablePoint | Position |
1769
+ | rotation | number | Rotation |
1770
+ | scale | TypeParsablePoint \| number | Scale |
1771
+ | color | TypeColor | Color |
1772
+ | transform | TypeParsableTransform | Full transform |
1773
+ | isShown | boolean | Visibility |
1774
+
1775
+ ```js
1776
+ figure.add({
1777
+ name: 'shape', make: 'polygon', sides: 4, radius: 0.3,
1778
+ mods: {
1779
+ scenarios: {
1780
+ center: { position: [0, 0], scale: 1, color: [1, 0, 0, 1] },
1781
+ right: { position: [1, 0], scale: 2, color: [0, 0, 1, 1] },
1782
+ },
1783
+ },
1784
+ });
1785
+
1786
+ // Set instantly
1787
+ figure.elements._shape.setScenario('center');
1788
+
1789
+ // Animate to scenario
1790
+ figure.elements._shape.animations.new()
1791
+ .scenario({ target: 'right', duration: 1 })
1792
+ .start();
1793
+
1794
+ // Set all children
1795
+ figure.elements.setScenarios('center');
1796
+ ```
1797
+
1798
+ ---
1799
+
1800
+ ## 10. SlideNavigator
1801
+
1802
+ Manages sequential presentation states with animated transitions.
1803
+
1804
+ ### COL_SlideNavigator (`make: 'collections.slideNavigator'`)
1805
+
1806
+ | Property | Type | Default | Description |
1807
+ |---|---|---|---|
1808
+ | collection | Figure \| FigureElementCollection | parent | Collection to manage |
1809
+ | slides | OBJ_SlideNavigatorSlide[] | | Slide definitions |
1810
+ | prevButton | object \| null | | Previous button |
1811
+ | nextButton | object \| null | | Next button |
1812
+ | text | OBJ_FormattedText \| null | | Text element |
1813
+ | equation | Equation \| string \| string[] | | Equation(s) |
1814
+ | equationDefaults | object | | Default equation animation |
1815
+ | disableOpacity | number | `0.7` | Disabled button opacity |
1816
+
1817
+ ### OBJ_SlideNavigatorSlide
1818
+
1819
+ | Property | Type | Description |
1820
+ |---|---|---|
1821
+ | show | TypeElementPath | Elements to show |
1822
+ | showCommon | TypeElementPath | Common elements to show (persists to subsequent slides) |
1823
+ | hide | TypeElementPath | Elements to hide |
1824
+ | hideCommon | TypeElementPath | Common elements to hide |
1825
+ | enterStateCommon | function | Common enter state callback |
1826
+ | enterState | function | Enter state callback |
1827
+ | transition | function \| object \| array | Transition animation (forward only) |
1828
+ | steadyStateCommon | function | Common steady state |
1829
+ | steadyState | function | Steady state callback |
1830
+ | leaveStateCommon | function | Common leave state |
1831
+ | leaveState | function | Leave state callback |
1832
+ | form | string \| string[] | Equation form(s) |
1833
+ | fromForm | string \| string[] | Previous form before transition |
1834
+ | text | OBJ_FormattedText | Text content |
1835
+ | scenarioCommon | string \| string[] | Common scenario |
1836
+ | scenario | string \| string[] | Scenario |
1837
+ | animate | `'move'` \| `'dissolve'` \| `'moveFrom'` \| `'pulse'` \| `'dissolveInThenMove'` | Form animation type |
1838
+ | clear | boolean | Don't inherit common properties |
1839
+ | modifiers | OBJ_TextModifiersDefinition | Text modifiers |
1840
+ | modifiersCommon | OBJ_TextModifiersDefinition | Common text modifiers |
1841
+ | time | string \| number | Recorder: absolute transition time |
1842
+ | delta | number | Recorder: time delta from last slide |
1843
+
1844
+ ### Slide Lifecycle
1845
+
1846
+ `enterState` → `transition(done)` → `steadyState`. Each slide should fully define its state.
1847
+
1848
+ The `transition` callback receives `(done, index, from)`. The `done` callback **must** be called to progress to steady state.
1849
+
1850
+ ### SlideNavigator Methods
1851
+
1852
+ ```js
1853
+ const nav = new Fig.SlideNavigator({ collection: figure.elements, slides });
1854
+ // or
1855
+ const nav = figure.addSlideNavigator();
1856
+ nav.loadSlides(slides);
1857
+
1858
+ nav.goToSlide(0);
1859
+ nav.nextSlide();
1860
+ nav.prevSlide();
1861
+ nav.currentSlideIndex; // Current index
1862
+ ```
1863
+
1864
+ ### Verbose Syntax
1865
+ ```js
1866
+ const slides = [
1867
+ {
1868
+ show: [eqn, description],
1869
+ steadyState: () => { eqn.showForm('0'); },
1870
+ },
1871
+ {
1872
+ show: [eqn, description],
1873
+ enterState: () => { eqn.showForm('0'); },
1874
+ transition: (done) => {
1875
+ eqn.animations.new()
1876
+ .goToForm({ target: '1', animate: 'move' })
1877
+ .whenFinished(done)
1878
+ .start();
1879
+ },
1880
+ steadyState: () => { eqn.showForm('1'); },
1881
+ },
1882
+ ];
1883
+ ```
1884
+
1885
+ ### Succinct Equation Syntax
1886
+ ```js
1887
+ const nav = new Fig.SlideNavigator({
1888
+ collection: figure.elements, equation: eqn, text: description,
1889
+ slides: [
1890
+ { form: '0', text: 'Start here' },
1891
+ { text: 'Next step' },
1892
+ { form: '1' },
1893
+ ],
1894
+ });
1895
+ ```
1896
+
1897
+ ### Shortcut Animation Syntax (OBJ_AnimationDefinition)
1898
+
1899
+ ```js
1900
+ nav.loadSlides([
1901
+ {
1902
+ showCommon: 'sq1',
1903
+ enterStateCommon: () => { sq1.setPosition([-0.5, 0.5]); },
1904
+ },
1905
+ { transition: { in: 'sq2' } }, // dissolve in
1906
+ { transition: { position: 'sq2', target: [0.3, 0.5], duration: 1 } }, // move
1907
+ {
1908
+ transition: [ // sequence
1909
+ { position: 'sq1', target: [-0.3, 0.5], duration: 1 },
1910
+ { rotation: 'sq1', target: Math.PI / 4, duration: 1 },
1911
+ ],
1912
+ },
1913
+ {
1914
+ transition: [ // parallel (nested)
1915
+ [
1916
+ { rotation: 'sq1', target: 0, duration: 1 },
1917
+ { rotation: 'sq2', target: 0, duration: 1 },
1918
+ ],
1919
+ { out: ['sq1', 'sq2'] }, // dissolve out
1920
+ ],
1921
+ },
1922
+ ]);
1923
+ ```
1924
+
1925
+ Shortcut animation keys: `in`, `out`, `position`, `rotation`, `scale`, `scenario`, `scenarios`, `dim`, `undim`, `pulse`, `trigger`, `delay`, `goToForm`.
1926
+
1927
+ ---
1928
+
1929
+ ## 11. FigureElement Methods
1930
+
1931
+ ### Transform Methods
1932
+ ```js
1933
+ element.setPosition([x, y]) // or setPosition(x, y)
1934
+ element.getPosition() // returns Point
1935
+ element.getPosition('figure') // in figure space
1936
+ element.setRotation(angle)
1937
+ element.getRotation() // returns number
1938
+ element.setScale(x, y) // or setScale(uniformScale)
1939
+ element.getScale() // returns Point
1940
+ element.setTransform(transform)
1941
+ element.getTransform() // returns Transform
1942
+ ```
1943
+
1944
+ ### Visibility Methods
1945
+ ```js
1946
+ element.show()
1947
+ element.hide()
1948
+ element.toggleShow()
1949
+ element.showAll() // Recursive
1950
+ element.hideAll() // Recursive
1951
+ // Collections:
1952
+ collection.show(elementPath) // Show specific children
1953
+ collection.hide(elementPath)
1954
+ collection.showOnly(elementPath) // Hide all, show specified
1955
+ ```
1956
+
1957
+ ### Color Methods
1958
+ ```js
1959
+ element.setColor([r, g, b, a])
1960
+ element.setOpacity(0.5) // 0-1
1961
+ element.dim() // Set dimColor
1962
+ element.undim() // Set defaultColor
1963
+ element.setDimColor([r, g, b, a])
1964
+ // Collections:
1965
+ collection.dim(elementPath) // Dim specific children
1966
+ collection.undim(elementPath)
1967
+ collection.highlight(elementPath) // Undim specified, dim rest
1968
+ ```
1969
+
1970
+ ### Scenario Methods
1971
+ ```js
1972
+ element.setScenario('name')
1973
+ element.setScenario({ position: [0, 0], rotation: 0, color: [1, 0, 0, 1] })
1974
+ element.saveScenario('name')
1975
+ element.getCurrentScenario()
1976
+ // Collections:
1977
+ collection.setScenarios('name') // All children
1978
+ collection.saveScenarios('name', ['position', 'rotation'])
1979
+ collection.getAllElementsWithScenario('name')
1980
+ ```
1981
+
1982
+ ### Touch/Move Methods
1983
+ ```js
1984
+ element.setTouchable() // or setTouchable(false)
1985
+ element.setMovable()
1986
+ element.setMove({ type: 'rotation', bounds: ... })
1987
+ element.isMoving()
1988
+ element.startMovingFreely()
1989
+ element.stopMovingFreely()
1990
+ element.click() // Programmatic click
1991
+ ```
1992
+
1993
+ ### Animation Methods
1994
+ ```js
1995
+ element.pulse({ scale: 1.5, duration: 1 })
1996
+ element.animations.new()...start()
1997
+ element.isAnimating()
1998
+ element.stopAnimating() // or 'freeze'|'cancel'|'complete'
1999
+ element.stopAnimating('complete', 'animName') // Stop specific animation
2000
+ element.getRemainingAnimationTime()
2001
+ element.getRemainingAnimationTime('animName')
2002
+ ```
2003
+
2004
+ Stop options: `'freeze'` (stop at current state), `'cancel'` (cancel), `'complete'` (jump to end), `'animateToComplete'` (animate to end), `'dissolveToComplete'` (dissolve to end).
2005
+
2006
+ ### Text Methods
2007
+ ```js
2008
+ textElement.setText('new text')
2009
+ textElement.setText({ text: 'new', font: { size: 0.3 }, xAlign: 'center' })
2010
+ ```
2011
+
2012
+ ### Collection Element Access
2013
+ ```js
2014
+ collection.getElement('child') // By name
2015
+ collection.getElement('a.b.c') // Dot-path access
2016
+ collection._childName // Direct property access
2017
+ collection.getChildren() // All direct children
2018
+ collection.getAllElements() // All recursive
2019
+ collection.getAllPrimitives() // All recursive primitives
2020
+ collection.add('name', element) // Add child
2021
+ collection.remove('name') // Remove child
2022
+ collection.toFront('name') // Change draw order
2023
+ collection.toBack('name')
2024
+ ```
2025
+
2026
+ ### Boundary Methods
2027
+ ```js
2028
+ element.getBoundingRect('figure') // In figure space
2029
+ element.getRelativeBoundingRect()
2030
+ element.getCenterFigurePosition()
2031
+ element.getBorder('figure', 'touchBorder')
2032
+ ```
2033
+
2034
+ ### Coordinate Space Transform
2035
+ ```js
2036
+ element.transformPoint([0, 0], 'local', 'figure')
2037
+ element.pointFromSpaceToSpace(point, 'draw', 'figure')
2038
+ // Spaces: 'draw', 'local', 'figure', 'gl', 'pixel'
2039
+ element.spaceTransformMatrix('local', 'figure')
2040
+ element.pixelToPlane(pixel, 'figure', Plane.xy())
2041
+ element.glToPlane(glPoint, 'figure')
2042
+ ```
2043
+
2044
+ ### Batch Transform/Color Methods (Collections)
2045
+ ```js
2046
+ collection.getElementTransforms()
2047
+ collection.setElementTransforms(transformsObj)
2048
+ collection.getElementColors()
2049
+ collection.setElementColors(colorsObj)
2050
+ collection.animateToTransforms(transformsObj, duration)
2051
+ collection.animateToColors(colorsObj, duration)
2052
+ ```
2053
+
2054
+ ---
2055
+
2056
+ ## 12. Geometry Utilities
2057
+
2058
+ ### Point Class
2059
+ ```js
2060
+ const p = new Fig.Point(1, 2); // or new Fig.Point(1, 2, 3) for 3D
2061
+ p.x; p.y; p.z;
2062
+ p.add(other) // Returns new Point
2063
+ p.sub(other)
2064
+ p.scale(scalar)
2065
+ p.length() // Distance from origin
2066
+ p.distance(otherPoint)
2067
+ p.normalize() // Unit vector
2068
+ p.rotate(angle, center?)
2069
+ p.round(precision)
2070
+ p.isEqualTo(other, precision?)
2071
+ p.dotProduct(other)
2072
+ p.crossProduct(other)
2073
+ p.toPolar() // { mag, angle }
2074
+ p.transformBy(matrix)
2075
+ ```
2076
+
2077
+ ### Transform Class
2078
+ ```js
2079
+ const t = new Fig.Transform()
2080
+ .translate(x, y)
2081
+ .rotate(angle)
2082
+ .scale(sx, sy);
2083
+
2084
+ t.t() // Get translation as Point
2085
+ t.r() // Get rotation angle
2086
+ t.s() // Get scale as Point
2087
+ t.matrix() // Get 4x4 matrix
2088
+ t.updateTranslation(point)
2089
+ t.updateRotation(angle)
2090
+ t.updateScale(scale)
2091
+ t.isEqualTo(other, precision?)
2092
+ t._dup() // Duplicate
2093
+ ```
2094
+
2095
+ ### Line Class
2096
+ ```js
2097
+ const l = new Fig.Line([0, 0], [1, 1]); // Finite segment
2098
+ const l2 = new Fig.Line([0, 0], [1, 1], 0); // Infinite line
2099
+ l.length()
2100
+ l.angle()
2101
+ l.midPoint()
2102
+ l.pointAtPercent(0.5)
2103
+ l.hasPointOn(point)
2104
+ l.distanceToPoint(point)
2105
+ l.intersectsWith(otherLine) // { intersect, collinear, onLines }
2106
+ l.isParallelTo(otherLine)
2107
+ l.offset('positive', distance)
2108
+ l.reverse()
2109
+ ```
2110
+
2111
+ ### Plane Class
2112
+ ```js
2113
+ const pl = new Fig.Plane([0, 0, 0], [0, 0, 1]); // point + normal
2114
+ Plane.xy(); Plane.xz(); Plane.yz();
2115
+ pl.hasPointOn(point)
2116
+ pl.distanceToPoint(point)
2117
+ pl.pointProjection(point)
2118
+ pl.intersectsWith(otherPlane) // Returns Line or null
2119
+ pl.lineIntersect(line) // Returns Point or null
2120
+ ```
2121
+
2122
+ ### Rect Class
2123
+ ```js
2124
+ const r = new Fig.Rect(left, bottom, width, height);
2125
+ r.left; r.bottom; r.right; r.top; r.width; r.height;
2126
+ r.center()
2127
+ r.isPointInside(point)
2128
+ ```
2129
+
2130
+ ### Utility Functions
2131
+ ```js
2132
+ Fig.range(start, stop, step) // [start, start+step, ..., stop]
2133
+ Fig.round(value, precision) // Round to N decimal places
2134
+ Fig.rand(min, max) // Random number
2135
+ Fig.randInt(min, max) // Random integer
2136
+ Fig.polarToRect(magnitude, angle) // Returns Point
2137
+ Fig.rectToPolar(x, y) // Returns { mag, angle }
2138
+ Fig.deg(radians) // Radians to degrees
2139
+ Fig.normAngle(angle) // Normalize to 0-2π
2140
+ Fig.clipAngle(angle, range) // Clip to range ('0to360', '-180to180')
2141
+ Fig.minAngleDiff(a1, a2) // Minimum absolute angle difference
2142
+ Fig.threePointAngle(p2, p1, p3) // Angle at p1
2143
+
2144
+ // 3D geometry functions
2145
+ Fig.surfaceGrid({ x, y, z }) // Generate surface point grid
2146
+ // x: [min, max, step], y: [min, max, step], z: (x, y) => number
2147
+
2148
+ // Conversion
2149
+ Fig.toNumbers(points) // Point[] → flat number[]
2150
+ Fig.toPoints(numbers) // flat number[] → Point[]
2151
+ Fig.numbersToPoints(numbers, dim?)
2152
+ Fig.pointsToNumbers(points, dim?)
2153
+
2154
+ // 3D shape vertex generators (return [points, normals])
2155
+ Fig.cube({ side })
2156
+ Fig.sphere({ radius, sides })
2157
+ Fig.cylinder({ radius, length, sides })
2158
+ Fig.cone({ radius, length, sides })
2159
+ Fig.revolve({ profile, sides })
2160
+ Fig.polygon({ sides, radius, center, close, direction })
2161
+ ```
2162
+
2163
+ ---
2164
+
2165
+ ## 13. OBJ_Scene
2166
+
2167
+ ### OBJ_Scene
2168
+
2169
+ | Property | Type | Default | Description |
2170
+ |---|---|---|---|
2171
+ | style | `'orthographic'` \| `'perspective'` | | Projection style (set for 3D) |
2172
+ | left | number | `-1` | 2D: left boundary |
2173
+ | right | number | `1` | 2D: right boundary |
2174
+ | bottom | number | `-1` | 2D: bottom boundary |
2175
+ | top | number | `1` | 2D: top boundary |
2176
+ | near | number | `0.1` | Near clip plane (3D) |
2177
+ | far | number | `100` | Far clip plane (3D) |
2178
+ | aspectRatio | number | | Aspect ratio override |
2179
+ | fieldOfView | number | | Perspective field of view (radians) |
2180
+ | light | OBJ_Light | | Lighting configuration |
2181
+ | camera | OBJ_Camera | | Camera configuration |
2182
+ | zoom | number | | Zoom level |
2183
+ | pan | TypeParsablePoint | | Pan offset |
2184
+
2185
+ ### OBJ_Camera
2186
+
2187
+ | Property | Type | Default | Description |
2188
+ |---|---|---|---|
2189
+ | position | TypeParsablePoint | `[0, 0, 2]` | Camera position |
2190
+ | lookAt | TypeParsablePoint | `[0, 0, 0]` | Look-at target |
2191
+ | up | TypeParsablePoint | `[0, 1, 0]` | Up vector |
2192
+
2193
+ ### OBJ_Light
2194
+
2195
+ | Property | Type | Default | Description |
2196
+ |---|---|---|---|
2197
+ | directional | TypeParsablePoint | `[0.7, 0.5, 1]` | Directional light vector |
2198
+ | ambient | number | `0.4` | Ambient light level (0-1) |
2199
+ | point | TypeParsablePoint | | Point light position |
2200
+
2201
+ ### 2D Scene
2202
+ ```js
2203
+ // Simple: scene as [x, y, width, height]
2204
+ new Fig.Figure({ scene: [-2, -2, 4, 4] });
2205
+ ```
2206
+
2207
+ ### 3D Scene
2208
+ ```js
2209
+ new Fig.Figure({
2210
+ scene: {
2211
+ style: 'orthographic', // or 'perspective'
2212
+ camera: { position: [2, 1, 2], lookAt: [0, 0, 0], up: [0, 1, 0] },
2213
+ light: { directional: [0.7, 0.5, 1], ambient: 0.4 },
2214
+ near: 0.1,
2215
+ far: 10,
2216
+ },
2217
+ });
2218
+ ```
2219
+
2220
+ ### Scene Methods
2221
+ ```js
2222
+ figure.scene.setCamera({ position: [1, 1, 2] });
2223
+ figure.scene.setLight({ directional: [0.7, 0.5, 1] });
2224
+ figure.scene.setProjection({ style: 'orthographic' });
2225
+ figure.scene.setPanZoom(pan, zoom);
2226
+ figure.scene.figureToGL(point);
2227
+ figure.scene.glToFigure(point);
2228
+ ```
2229
+
2230
+ ---
2231
+
2232
+ ## 14. Recorder
2233
+
2234
+ FigureOne supports recording and playback of figure interactions.
2235
+
2236
+ ```js
2237
+ const recorder = figure.recorder;
2238
+
2239
+ // Record
2240
+ recorder.start();
2241
+ // ... user interactions ...
2242
+ recorder.stop();
2243
+ recorder.save('recording.json');
2244
+
2245
+ // Playback
2246
+ recorder.load('recording.json');
2247
+ recorder.play();
2248
+ recorder.pause();
2249
+ recorder.seek(timeInSeconds);
2250
+ ```
2251
+
2252
+ The recorder captures all element state changes (transforms, colors, visibility) and user interactions. Playback recreates the exact sequence. SlideNavigator slides can include `time` and `delta` properties for automatic recorder timing.
2253
+
2254
+ ---
2255
+
2256
+ ## 15. Common Patterns and Tips
2257
+
2258
+ ### Element Naming and Access
2259
+ ```js
2260
+ // Named elements are accessible as underscore-prefixed properties
2261
+ figure.add({ make: 'polygon', name: 'myShape', sides: 6 });
2262
+ figure.elements._myShape;
2263
+
2264
+ // Or via getElement with dot-path
2265
+ figure.getElement('myShape');
2266
+
2267
+ // Nested collections
2268
+ figure.add({
2269
+ make: 'collection',
2270
+ name: 'group',
2271
+ elements: [
2272
+ { make: 'polygon', name: 'a', sides: 4 },
2273
+ { make: 'polygon', name: 'b', sides: 6 },
2274
+ ],
2275
+ });
2276
+ figure.getElement('group.a');
2277
+ figure.elements._group._a;
2278
+ ```
2279
+
2280
+ ### mods Property
2281
+ All elements support a `mods` property for setting any FigureElement property after construction:
2282
+
2283
+ ```js
2284
+ figure.add({
2285
+ make: 'polygon', sides: 6, radius: 0.3,
2286
+ mods: {
2287
+ isTouchable: true,
2288
+ touchBorder: 0.1,
2289
+ dimColor: [0.7, 0.7, 0.7, 1],
2290
+ scenarios: {
2291
+ default: { position: [0, 0] },
2292
+ moved: { position: [0.5, 0.5] },
2293
+ },
2294
+ },
2295
+ });
2296
+ ```
2297
+
2298
+ ### Equation Element Naming Conventions
2299
+ - `'elementName'` — text element displaying `elementName`
2300
+ - `'_ text'` — text element displaying ` text` (underscore prefix stripped)
2301
+ - `'a_1'`, `'a_2'` — copies of `a` (number suffix is index)
2302
+ - `{ symbol: 'type' }` — symbol element
2303
+ - Undeclared strings in forms are auto-created as text elements
2304
+
2305
+ ### Inline Symbol Shortcuts
2306
+ Symbols can be created inline in forms without pre-declaring in elements:
2307
+ ```js
2308
+ // One-use inline symbol
2309
+ { frac: ['a', 'vinculum', 'b'] } // Auto-creates vinculum symbol
2310
+
2311
+ // Named inline with customization
2312
+ { frac: ['a', { vinculum: { lineWidth: 0.004 } }, 'b'] }
2313
+
2314
+ // Multiple uses need unique names (suffix with _name)
2315
+ { frac: ['a', 'v1_vinculum', { frac: ['c', 'v2_vinculum', 'd'] }] }
2316
+ ```
2317
+
2318
+ ### Coordinate Spaces
2319
+ - `'draw'` — Drawing canvas coordinates (element's own draw space)
2320
+ - `'local'` — Element's local space (after element's transform)
2321
+ - `'figure'` — Figure root coordinates (what you see on screen)
2322
+ - `'gl'` — WebGL clip coordinates (-1 to 1)
2323
+ - `'pixel'` — Screen pixel coordinates
2324
+
2325
+ ### TypeParsableBounds
2326
+ Movement bounds can be defined as:
2327
+ ```js
2328
+ // Rectangle bounds
2329
+ { left: -1, bottom: -1, right: 1, top: 1 }
2330
+
2331
+ // Or as a Rect
2332
+ new Fig.Rect(-1, -1, 2, 2)
2333
+
2334
+ // Rotation bounds
2335
+ { min: 0, max: Math.PI }
2336
+ ```
2337
+
2338
+ ### FigureElement Notifications
2339
+ Common notification events:
2340
+ - `'setTransform'` — element transform changed
2341
+ - `'onClick'` — element clicked/touched
2342
+ - `'animationsFinished'` — all animations completed
2343
+ - `'setFigure'` — element added to figure
2344
+ - `'beforeDraw'` — before element is drawn
2345
+ - `'afterDraw'` — after element is drawn
2346
+ - `'setColor'` — color changed
2347
+
2348
+ ```js
2349
+ element.notifications.add('setTransform', (transform) => {
2350
+ console.log('New position:', transform.t());
2351
+ });
2352
+ element.notifications.remove('setTransform');
2353
+ ```
2354
+
2355
+ ### Animation Progression Functions
2356
+ Built-in progression functions:
2357
+ - `'linear'` — constant speed
2358
+ - `'easeinout'` — slow start and end (default for most steps)
2359
+ - `'easein'` — slow start
2360
+ - `'easeout'` — slow end
2361
+ - Custom: `(percent) => mappedPercent` (0-1 input, 0-1 output)
2362
+
2363
+ ---
2364
+
2365
+ ## 16. Complete Examples
2366
+
2367
+ ### Interactive Geometry Figure
2368
+
2369
+ ```js
2370
+ const figure = new Fig.Figure({ scene: [-2, -1.5, 4, 3] });
2371
+
2372
+ // Triangle with labeled sides and angles
2373
+ const tri = figure.add({
2374
+ make: 'collections.polyline',
2375
+ points: [[-1, -0.5], [1, -0.5], [0, 0.8]],
2376
+ close: true, width: 0.02,
2377
+ color: [0, 0.5, 1, 1],
2378
+ side: {
2379
+ label: { text: null, precision: 1 },
2380
+ offset: 0.15,
2381
+ },
2382
+ angle: {
2383
+ curve: { radius: 0.25, width: 0.01 },
2384
+ label: { text: null, precision: 0 },
2385
+ },
2386
+ pad: {
2387
+ color: [0, 0.5, 1, 0.3],
2388
+ radius: 0.15,
2389
+ isMovable: true,
2390
+ boundary: [-1.8, -1.3, 3.6, 2.6],
2391
+ },
2392
+ });
2393
+ ```
2394
+
2395
+ ### Equation with Animated Form Transitions
2396
+
2397
+ ```js
2398
+ const eqn = figure.add({
2399
+ make: 'equation',
2400
+ elements: {
2401
+ v: { symbol: 'vinculum' },
2402
+ lb: { symbol: 'bracket', side: 'left' },
2403
+ rb: { symbol: 'bracket', side: 'right' },
2404
+ r: { symbol: 'radical' },
2405
+ equals: ' = ',
2406
+ plus: ' + ',
2407
+ minus: ' \u2212 ',
2408
+ _4: '4',
2409
+ },
2410
+ phrases: {
2411
+ bPlusMinus: ['b', 'plus', { root: ['r', [{ sup: ['b_1', '2_1'] }, 'minus', '_4', 'a', 'c']] }],
2412
+ },
2413
+ formDefaults: { alignment: { fixTo: 'equals' } },
2414
+ forms: {
2415
+ quadratic: ['a', { sup: ['x_1', '2'] }, 'plus', 'b_2', 'x_2', 'plus', 'c_1', 'equals', '0'],
2416
+ solution: ['x', 'equals', { frac: [['minus', 'b', 'plus', { root: ['r', [{ sup: ['b_1', '2_1'] }, 'minus', '_4', 'a', 'c']] }], 'v', ['2', 'a_1']] }],
2417
+ },
2418
+ formSeries: { default: ['quadratic', 'solution'] },
2419
+ });
2420
+
2421
+ // Animate between forms
2422
+ eqn.showForm('quadratic');
2423
+ eqn.animations.new()
2424
+ .delay(2)
2425
+ .goToForm({ target: 'solution', animate: 'move', duration: 2 })
2426
+ .start();
2427
+ ```
2428
+
2429
+ ### SlideNavigator with Equations and Text
2430
+
2431
+ ```js
2432
+ const figure = new Fig.Figure();
2433
+
2434
+ const description = figure.add({
2435
+ make: 'ftext',
2436
+ font: { size: 0.08, family: 'Arial' },
2437
+ xAlign: 'center',
2438
+ position: [0, -0.6],
2439
+ });
2440
+
2441
+ const eqn = figure.add({
2442
+ make: 'equation',
2443
+ elements: { v: { symbol: 'vinculum' }, equals: ' = ' },
2444
+ forms: {
2445
+ 0: ['a', 'equals', { frac: ['b', 'v', 'c'] }],
2446
+ 1: ['a', 'equals', { frac: [['b', '_ + ', 'd'], 'v', 'c'] }],
2447
+ 2: ['a', 'equals', { frac: [['b', '_ + ', 'd'], 'v', ['c', '_ + ', 'e']] }],
2448
+ },
2449
+ formSeries: { default: ['0', '1', '2'] },
2450
+ });
2451
+
2452
+ const nav = figure.addSlideNavigator({
2453
+ equation: eqn,
2454
+ text: description,
2455
+ slides: [
2456
+ { form: '0', text: 'Start with a simple fraction' },
2457
+ { form: '1', text: 'Add |d| to the numerator', modifiers: { d: { font: { color: [1, 0, 0, 1] } } } },
2458
+ { form: '2', text: 'Add |e| to the denominator', modifiers: { e: { font: { color: [0, 0, 1, 1] } } } },
2459
+ ],
2460
+ });
2461
+ nav.goToSlide(0);
2462
+ ```
2463
+
2464
+ ### 3D Scene with Multiple Shapes
2465
+
2466
+ ```js
2467
+ const figure = new Fig.Figure({
2468
+ scene: {
2469
+ style: 'orthographic',
2470
+ camera: { position: [2, 1, 2], lookAt: [0, 0, 0] },
2471
+ light: { directional: [0.7, 0.5, 1], ambient: 0.4 },
2472
+ },
2473
+ });
2474
+
2475
+ figure.add([
2476
+ { make: 'cube', side: 0.3, position: [-0.5, 0, 0], color: [1, 0, 0, 1] },
2477
+ { make: 'sphere', radius: 0.2, sides: 20, normals: 'curve', position: [0, 0, 0], color: [0, 0.7, 0, 1] },
2478
+ { make: 'cylinder', radius: 0.15, length: 0.4, sides: 20, normals: 'curve', position: [0.5, 0, 0], color: [0, 0.3, 1, 1] },
2479
+ { make: 'collections.axis3', length: 0.8, width: 0.01 },
2480
+ { make: 'cameraControl' },
2481
+ ]);
2482
+ ```
2483
+
2484
+ ### Plot with Zoom and Pan
2485
+
2486
+ ```js
2487
+ const x = Fig.range(0, 4 * Math.PI, 0.05);
2488
+ const y = x.map(v => Math.sin(v));
2489
+
2490
+ figure.add({
2491
+ make: 'collections.plot',
2492
+ width: 1.6, height: 1, position: [-0.8, -0.5],
2493
+ x: { start: 0, stop: 4 * Math.PI, title: 'x', step: Math.PI, labels: { format: v => `${(v / Math.PI).toFixed(0)}π` } },
2494
+ y: { start: -1.2, stop: 1.2, title: 'sin(x)', step: 0.5 },
2495
+ trace: { points: x.map((v, i) => [v, y[i]]), color: [1, 0, 0, 1] },
2496
+ zoom: 'xy',
2497
+ pan: 'xy',
2498
+ });
2499
+ ```