@motionscript/plot 0.0.0-stage → 0.1.0-alpha.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +201 -0
  3. package/dist/browser/chunks/chunk-37IWXGPH.js +2 -0
  4. package/dist/browser/chunks/chunk-37IWXGPH.js.map +7 -0
  5. package/dist/browser/index.js +84 -0
  6. package/dist/browser/index.js.map +7 -0
  7. package/dist/browser/kit.js +2 -0
  8. package/dist/browser/kit.js.map +7 -0
  9. package/dist/browser/manifest.json +12 -0
  10. package/dist/engine.d.ts +14 -0
  11. package/dist/engine.d.ts.map +1 -0
  12. package/dist/engine.js +14 -0
  13. package/dist/engine.js.map +1 -0
  14. package/dist/graph2d/curve-cache.d.ts +60 -0
  15. package/dist/graph2d/curve-cache.d.ts.map +1 -0
  16. package/dist/graph2d/curve-cache.js +79 -0
  17. package/dist/graph2d/curve-cache.js.map +1 -0
  18. package/dist/graph2d/curve-error.d.ts +12 -0
  19. package/dist/graph2d/curve-error.d.ts.map +1 -0
  20. package/dist/graph2d/curve-error.js +15 -0
  21. package/dist/graph2d/curve-error.js.map +1 -0
  22. package/dist/graph2d/curve.d.ts +104 -0
  23. package/dist/graph2d/curve.d.ts.map +1 -0
  24. package/dist/graph2d/curve.js +531 -0
  25. package/dist/graph2d/curve.js.map +1 -0
  26. package/dist/graph2d/graph2d.d.ts +164 -0
  27. package/dist/graph2d/graph2d.d.ts.map +1 -0
  28. package/dist/graph2d/graph2d.js +404 -0
  29. package/dist/graph2d/graph2d.js.map +1 -0
  30. package/dist/graph2d/index.d.ts +32 -0
  31. package/dist/graph2d/index.d.ts.map +1 -0
  32. package/dist/graph2d/index.js +32 -0
  33. package/dist/graph2d/index.js.map +1 -0
  34. package/dist/graph2d/plane-fill.d.ts +110 -0
  35. package/dist/graph2d/plane-fill.d.ts.map +1 -0
  36. package/dist/graph2d/plane-fill.js +248 -0
  37. package/dist/graph2d/plane-fill.js.map +1 -0
  38. package/dist/graph2d/plane.d.ts +179 -0
  39. package/dist/graph2d/plane.d.ts.map +1 -0
  40. package/dist/graph2d/plane.js +359 -0
  41. package/dist/graph2d/plane.js.map +1 -0
  42. package/dist/graph2d/shared.d.ts +40 -0
  43. package/dist/graph2d/shared.d.ts.map +1 -0
  44. package/dist/graph2d/shared.js +70 -0
  45. package/dist/graph2d/shared.js.map +1 -0
  46. package/dist/graph3d/expression.d.ts +30 -0
  47. package/dist/graph3d/expression.d.ts.map +1 -0
  48. package/dist/graph3d/expression.js +35 -0
  49. package/dist/graph3d/expression.js.map +1 -0
  50. package/dist/graph3d/graph3d.d.ts +186 -0
  51. package/dist/graph3d/graph3d.d.ts.map +1 -0
  52. package/dist/graph3d/graph3d.js +404 -0
  53. package/dist/graph3d/graph3d.js.map +1 -0
  54. package/dist/graph3d/index.d.ts +22 -0
  55. package/dist/graph3d/index.d.ts.map +1 -0
  56. package/dist/graph3d/index.js +22 -0
  57. package/dist/graph3d/index.js.map +1 -0
  58. package/dist/graph3d/shared.d.ts +61 -0
  59. package/dist/graph3d/shared.d.ts.map +1 -0
  60. package/dist/graph3d/shared.js +101 -0
  61. package/dist/graph3d/shared.js.map +1 -0
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +4 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/kit/equations.d.ts +56 -0
  67. package/dist/kit/equations.d.ts.map +1 -0
  68. package/dist/kit/equations.js +63 -0
  69. package/dist/kit/equations.js.map +1 -0
  70. package/dist/kit/expression.d.ts +60 -0
  71. package/dist/kit/expression.d.ts.map +1 -0
  72. package/dist/kit/expression.js +268 -0
  73. package/dist/kit/expression.js.map +1 -0
  74. package/dist/kit/index.d.ts +15 -0
  75. package/dist/kit/index.d.ts.map +1 -0
  76. package/dist/kit/index.js +13 -0
  77. package/dist/kit/index.js.map +1 -0
  78. package/dist/nodes.d.ts +19 -0
  79. package/dist/nodes.d.ts.map +1 -0
  80. package/dist/nodes.js +19 -0
  81. package/dist/nodes.js.map +1 -0
  82. package/package.json +68 -3
  83. package/registry.json +34 -0
  84. package/src/engine.ts +13 -0
  85. package/src/graph2d/curve-cache.ts +103 -0
  86. package/src/graph2d/curve-error.ts +19 -0
  87. package/src/graph2d/curve.ts +657 -0
  88. package/src/graph2d/graph2d.ts +586 -0
  89. package/src/graph2d/index.ts +31 -0
  90. package/src/graph2d/plane-fill.ts +308 -0
  91. package/src/graph2d/plane.ts +457 -0
  92. package/src/graph2d/shared.ts +103 -0
  93. package/src/graph3d/expression.ts +51 -0
  94. package/src/graph3d/graph3d.ts +615 -0
  95. package/src/graph3d/index.ts +21 -0
  96. package/src/graph3d/shared.ts +143 -0
  97. package/src/index.ts +3 -0
  98. package/src/kit/equations.ts +102 -0
  99. package/src/kit/expression.ts +323 -0
  100. package/src/kit/index.ts +29 -0
  101. package/src/nodes.ts +19 -0
  102. package/README.md +0 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expression.js","sourceRoot":"","sources":["../../src/kit/expression.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The little language every graph node's equation tiles are written in — the\n * one binding behind `z = f(x, y)` on the 3D graph and `y = f(x)` on the 2D one.\n *\n * **The grammar is not here.** It is the engine's `expressionOps.parse`\n * (from `@motionscript/sdk`), shared with the `\"=4[g]+3\"` expressions a scene\n * document writes in props. There used to be two parsers, and they agreed on\n * every rule they documented while disagreeing everywhere they hadn't: this one\n * scanned `1.2.3` as a single token that `Number` turned into a `NaN` which\n * parsed, compiled and rendered an invisible surface with no diagnostic\n * anywhere; it treated `\\r` as an unexpected character; and it checked no arity\n * on its variadic functions, so `atan2(1)` was a silent `NaN` too.\n *\n * What is genuinely this language's own is a **dialect** and a back end:\n *\n * - {@link PLOT_DIALECT} — trig in **radians** (a plot of `sin(x)` is a plot of\n * `sin(x)`, where a document's `rotation` is in degrees like every other angle\n * in the library), the hyperbolics and log bases a plot wants, `phi`, and\n * references spelled as bare identifiers rather than `[id]`.\n * - {@link compileExpression} — the emitter, which turns the shared AST into a\n * two-argument closure rather than into the signal binding a document prop\n * needs.\n *\n * **Deliberately not `new Function(...)`.** An expression is a value typed into\n * the inspector, stored in a scene document and shared with whoever opens it\n * next; compiling it as JavaScript would make every scene file an execution\n * vector. The shared parser accepts a fixed grammar and can only ever produce\n * arithmetic.\n *\n * ## Which letters are variables is the caller's business\n *\n * What differs between the two graphs is *how many free variables there are*,\n * and that matters more than it sounds. A 2D curve compiled with `x` alone\n * rejects `y` as an unknown identifier, so typing `y = x + y` says so in the\n * tile instead of silently plotting `x` — and a caller cannot forget to check,\n * because the check is the same one that catches `alert(1)`.\n */\nimport { expressionOps } from \"@motionscript/sdk\"\nimport type { ExpressionDialect, ExpressionNode } from \"@motionscript/sdk\"\n\n/**\n * A compiled expression: pure, and safe to call thousands of times per frame.\n *\n * Always two arguments regardless of how many variables it was compiled with,\n * because the arity is a *parse-time* fact and the signature is a hot path — a\n * variadic form would allocate an array per sample. A one-variable expression\n * simply never reads the second, so a curve calls it as `sample(x, 0)`.\n */\nexport type CompiledExpression = (x: number, y: number) => number\n\n/** The two free variables a `z = f(x, y)` surface is sampled over. */\nexport const SURFACE_VARIABLES = [\"x\", \"y\"] as const\n\n/** The one free variable a `y = f(x)` curve is sampled over. */\nexport const CURVE_VARIABLES = [\"x\"] as const\n\n/**\n * The names bound to the two argument slots, in order.\n *\n * At most two, because {@link CompiledExpression} has two argument slots — see\n * the note there on why that is fixed rather than variadic.\n */\nexport type ExpressionVariables = readonly string[]\n\n/** Single-argument functions available to an expression. */\nconst UNARY_FNS: Record<string, (v: number) => number> = {\n sin: Math.sin,\n cos: Math.cos,\n tan: Math.tan,\n asin: Math.asin,\n acos: Math.acos,\n atan: Math.atan,\n sinh: Math.sinh,\n cosh: Math.cosh,\n tanh: Math.tanh,\n sqrt: Math.sqrt,\n cbrt: Math.cbrt,\n abs: Math.abs,\n exp: Math.exp,\n log: Math.log,\n ln: Math.log,\n log2: Math.log2,\n log10: Math.log10,\n floor: Math.floor,\n ceil: Math.ceil,\n round: Math.round,\n sign: Math.sign,\n trunc: Math.trunc,\n}\n\n/** Multi-argument functions. */\nconst NARY_FNS: Record<string, (...args: number[]) => number> = {\n atan2: Math.atan2,\n pow: Math.pow,\n hypot: Math.hypot,\n min: Math.min,\n max: Math.max,\n mod: (a, b) => a % b,\n}\n\n/**\n * How many arguments each of {@link NARY_FNS} takes.\n *\n * Written from what this language already accepts, not copied from core's table:\n * `hypot(3)` and `min(3)` are meaningful and have always compiled, so they stay\n * `[1, Infinity]` where the document dialect has `hypot: [2, Infinity]`. What\n * the arity check is actually for is the other end — `atan2(1)` and `min()`\n * used to compile to a silent `NaN` and an `Infinity`, which rendered as\n * nothing at all and said nothing about why.\n */\nconst NARY_ARITY: Record<string, [number, number]> = {\n atan2: [2, 2],\n pow: [2, 2],\n mod: [2, 2],\n hypot: [1, Infinity],\n min: [1, Infinity],\n max: [1, Infinity],\n}\n\nconst CONSTANTS: Record<string, number> = {\n pi: Math.PI,\n PI: Math.PI,\n e: Math.E,\n E: Math.E,\n tau: Math.PI * 2,\n phi: (1 + Math.sqrt(5)) / 2,\n}\n\n/** The equation language, as a dialect of the shared grammar. */\nexport const PLOT_DIALECT: ExpressionDialect = {\n functions: {\n ...Object.fromEntries(\n Object.keys(UNARY_FNS).map((name) => [name, { arity: [1, 1] as [number, number] }])\n ),\n ...Object.fromEntries(\n Object.entries(NARY_ARITY).map(([name, arity]) => [name, { arity }])\n ),\n },\n constants: CONSTANTS,\n refs: \"bare\",\n unknownName: (name) => `Unknown identifier '${name}'`,\n}\n\n/** Everything an expression may name, minus the variables. */\nconst NAMEABLE = {\n functions: [...Object.keys(UNARY_FNS), ...Object.keys(NARY_FNS)].sort(),\n /** Lower-case spellings only; `PI`/`E` are accepted but not advertised twice. */\n constants: [\"pi\", \"e\", \"tau\", \"phi\"] as const,\n}\n\n/** What a tile's help popover lists, for a node with these free variables. */\nexport interface ExpressionVocabulary {\n variables: readonly string[]\n functions: readonly string[]\n constants: readonly string[]\n}\n\n/**\n * Everything an expression over `variables` may name, for the inspector's\n * \"what can I write here\" hint.\n *\n * Derived from the parser's own tables rather than re-listed on the client, so\n * the help can never claim a function the grammar doesn't have — which is the\n * way this kind of reference always rots.\n */\nexport function expressionVocabulary(\n variables: ExpressionVariables\n): ExpressionVocabulary {\n return { variables, ...NAMEABLE }\n}\n\n/** {@link expressionVocabulary} for the 3D graph's `z = f(x, y)` surfaces. */\nexport const EXPRESSION_VOCABULARY = expressionVocabulary(SURFACE_VARIABLES)\n\n/** {@link expressionVocabulary} for the 2D graph's `y = f(x)` curves. */\nexport const CURVE_VOCABULARY = expressionVocabulary(CURVE_VARIABLES)\n\n/**\n * Compiles `source` into a callable over `variables`. Throws on a syntax error,\n * so a caller rendering user-typed text should catch and skip that equation —\n * see {@link compileExpressionCached}, which does exactly that.\n */\nexport function compileExpression(\n source: string,\n variables: ExpressionVariables = SURFACE_VARIABLES\n): CompiledExpression {\n // The parser folds a constant before it will yield a reference, so a name that\n // is both would silently stop being the variable it was passed as.\n for (const name of variables) {\n if (name in CONSTANTS) {\n throw new Error(`'${name}' is a constant, so it cannot also be a free variable`)\n }\n }\n return emit(expressionOps.parse(source, PLOT_DIALECT).root, variables)\n}\n\n/** The shared AST into this language's two-argument closure. */\nfunction emit(\n node: ExpressionNode,\n variables: ExpressionVariables\n): CompiledExpression {\n switch (node.kind) {\n case \"const\": {\n const { value } = node\n return () => value\n }\n case \"ref\": {\n // Which argument slot this name reads, or -1 for a name that isn't a\n // variable *here* — the whole point of taking the list: `y` in a curve is\n // an unknown identifier, not a silent zero.\n const slot = variables.indexOf(node.name)\n if (slot === 0) return (x) => x\n if (slot === 1) return (_x, y) => y\n throw new Error(PLOT_DIALECT.unknownName(node.name))\n }\n case \"unary\": {\n const operand = emit(node.operand, variables)\n return (x, y) => -operand(x, y)\n }\n case \"binary\": {\n const l = emit(node.left, variables)\n const r = emit(node.right, variables)\n switch (node.op) {\n case \"+\": return (x, y) => l(x, y) + r(x, y)\n case \"-\": return (x, y) => l(x, y) - r(x, y)\n case \"*\": return (x, y) => l(x, y) * r(x, y)\n case \"/\": return (x, y) => l(x, y) / r(x, y)\n case \"%\": return (x, y) => l(x, y) % r(x, y)\n case \"^\": return (x, y) => Math.pow(l(x, y), r(x, y))\n }\n break\n }\n case \"call\": {\n const args = node.args.map((arg) => emit(arg, variables))\n const unary = UNARY_FNS[node.name]\n if (unary) {\n const [a] = args\n return (x, y) => unary(a(x, y))\n }\n const nary = NARY_FNS[node.name]\n // Specialise the common arity so the hot path doesn't allocate an array\n // per sample.\n if (args.length === 2) {\n const [a, b] = args\n return (x, y) => nary(a(x, y), b(x, y))\n }\n return (x, y) => nary(...args.map((arg) => arg(x, y)))\n }\n }\n throw new Error(\"Unsupported expression node\")\n}\n\n/**\n * Why an expression won't compile, in the terms the tile shows: the message and\n * nothing else. `null` means it compiles.\n *\n * Separate from {@link compileExpressionCached} because the two callers want\n * opposite things — the renderer wants the function or nothing, the inspector\n * wants the *reason* — and both must agree, which they do by going through the\n * same parser.\n */\nexport function expressionError(\n source: string,\n variables: ExpressionVariables = SURFACE_VARIABLES\n): string | null {\n const trimmed = source.trim()\n // Empty is not an error: it is a tile nobody has filled in yet, which the\n // panel already shows as a placeholder rather than as a mistake.\n if (trimmed === \"\") return null\n try {\n compileExpression(trimmed, variables)\n return null\n } catch (error) {\n return error instanceof Error ? error.message : String(error)\n }\n}\n\n/**\n * Compiles with memoisation, returning `null` for anything that doesn't parse.\n *\n * Both halves matter here. A node's builder re-runs every frame, so an uncached\n * compile would re-parse every equation sixty times a second; and the inspector\n * commits on every keystroke, so half-typed source reaches the renderer\n * constantly and must be *skipped* rather than thrown out of the render pass.\n *\n * The cache is also what makes the equation tween cheap: equal source compiles\n * to the identical closure, so \"did this equation change\" is an `===`. Note it\n * stores a failure as `null` and tests `hit !== undefined`, so unparseable\n * source is not re-parsed on every frame either.\n *\n * Keyed by the variable list as well as the source, because the same text means\n * different things under different bindings — `x + y` compiles for a surface and\n * is an error for a curve, and one cache would hand the surface's answer to the\n * curve that asked second.\n */\nconst caches = new Map<string, Map<string, CompiledExpression | null>>()\n\nexport function compileExpressionCached(\n source: string,\n variables: ExpressionVariables = SURFACE_VARIABLES\n): CompiledExpression | null {\n const trimmed = source.trim()\n if (trimmed === \"\") return null\n\n const key = variables.join(\",\")\n let cache = caches.get(key)\n if (!cache) {\n cache = new Map()\n caches.set(key, cache)\n }\n\n const hit = cache.get(trimmed)\n if (hit !== undefined) return hit\n\n let compiled: CompiledExpression | null\n try {\n compiled = compileExpression(trimmed, variables)\n } catch {\n compiled = null\n }\n cache.set(trimmed, compiled)\n return compiled\n}\n"]}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The parts both graph nodes are built from — the expression language their
3
+ * tiles are written in, and what a resolved equation list is.
4
+ *
5
+ * A kit rather than a base class, for the same reason `view3d-kit` is one: the
6
+ * two graphs share a *vocabulary*, not a shape. One draws lit meshes through a
7
+ * 3D pass and the other draws stroked polylines into the 2D scene, and the only
8
+ * things they genuinely agree on are what an author is allowed to type and what
9
+ * happens to a list of those when it is animated.
10
+ */
11
+ export { compileExpression, compileExpressionCached, expressionError, expressionVocabulary, CURVE_VARIABLES, CURVE_VOCABULARY, EXPRESSION_VOCABULARY, SURFACE_VARIABLES, } from "./expression.js";
12
+ export type { CompiledExpression, ExpressionVariables, ExpressionVocabulary, } from "./expression.js";
13
+ export { lerpGraphEquations } from "./equations.js";
14
+ export type { BlendSample, GraphEquationResolved } from "./equations.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/kit/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The parts both graph nodes are built from — the expression language their
3
+ * tiles are written in, and what a resolved equation list is.
4
+ *
5
+ * A kit rather than a base class, for the same reason `view3d-kit` is one: the
6
+ * two graphs share a *vocabulary*, not a shape. One draws lit meshes through a
7
+ * 3D pass and the other draws stroked polylines into the 2D scene, and the only
8
+ * things they genuinely agree on are what an author is allowed to type and what
9
+ * happens to a list of those when it is animated.
10
+ */
11
+ export { compileExpression, compileExpressionCached, expressionError, expressionVocabulary, CURVE_VARIABLES, CURVE_VOCABULARY, EXPRESSION_VOCABULARY, SURFACE_VARIABLES, } from "./expression.js";
12
+ export { lerpGraphEquations } from "./equations.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/kit/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAOrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA","sourcesContent":["/**\n * The parts both graph nodes are built from — the expression language their\n * tiles are written in, and what a resolved equation list is.\n *\n * A kit rather than a base class, for the same reason `view3d-kit` is one: the\n * two graphs share a *vocabulary*, not a shape. One draws lit meshes through a\n * 3D pass and the other draws stroked polylines into the 2D scene, and the only\n * things they genuinely agree on are what an author is allowed to type and what\n * happens to a list of those when it is animated.\n */\n\nexport {\n compileExpression,\n compileExpressionCached,\n expressionError,\n expressionVocabulary,\n CURVE_VARIABLES,\n CURVE_VOCABULARY,\n EXPRESSION_VOCABULARY,\n SURFACE_VARIABLES,\n} from \"./expression\"\nexport type {\n CompiledExpression,\n ExpressionVariables,\n ExpressionVocabulary,\n} from \"./expression\"\n\nexport { lerpGraphEquations } from \"./equations\"\nexport type { BlendSample, GraphEquationResolved } from \"./equations\"\n"]}
@@ -0,0 +1,19 @@
1
+ import { Graph2D } from "./graph2d/index.js";
2
+ import { Graph3D } from "./graph3d/index.js";
3
+ /**
4
+ * Every node type this package publishes.
5
+ *
6
+ * A host registers these by handing the array to an engine —
7
+ * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
8
+ * key. The decorator only *declares* that key: nothing is registered by the mere
9
+ * act of importing a module, so a registry holds exactly what its owner asked
10
+ * for and two engines in one process can differ about it.
11
+ *
12
+ * Listing class **values** is also what survives bundling. This package declares
13
+ * `sideEffects: false`, which licenses a bundler to drop a module nothing
14
+ * imports a value from, and a document names a node type by string — so
15
+ * `import "./graph2d/index.js"` for its side effect is exactly what gets shaken out, where an
16
+ * array of bindings is a data dependency that cannot be.
17
+ */
18
+ export declare const NODES: (typeof Graph2D | typeof Graph3D)[];
19
+ //# sourceMappingURL=nodes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,KAAK,qCAAqB,CAAC"}
package/dist/nodes.js ADDED
@@ -0,0 +1,19 @@
1
+ import { Graph2D } from "./graph2d/index.js";
2
+ import { Graph3D } from "./graph3d/index.js";
3
+ /**
4
+ * Every node type this package publishes.
5
+ *
6
+ * A host registers these by handing the array to an engine —
7
+ * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
8
+ * key. The decorator only *declares* that key: nothing is registered by the mere
9
+ * act of importing a module, so a registry holds exactly what its owner asked
10
+ * for and two engines in one process can differ about it.
11
+ *
12
+ * Listing class **values** is also what survives bundling. This package declares
13
+ * `sideEffects: false`, which licenses a bundler to drop a module nothing
14
+ * imports a value from, and a document names a node type by string — so
15
+ * `import "./graph2d/index.js"` for its side effect is exactly what gets shaken out, where an
16
+ * array of bindings is a data dependency that cannot be.
17
+ */
18
+ export const NODES = [Graph2D, Graph3D];
19
+ //# sourceMappingURL=nodes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodes.js","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC","sourcesContent":["import { Graph2D } from \"./graph2d\";\nimport { Graph3D } from \"./graph3d\";\n\n/**\n * Every node type this package publishes.\n *\n * A host registers these by handing the array to an engine —\n * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`\n * key. The decorator only *declares* that key: nothing is registered by the mere\n * act of importing a module, so a registry holds exactly what its owner asked\n * for and two engines in one process can differ about it.\n *\n * Listing class **values** is also what survives bundling. This package declares\n * `sideEffects: false`, which licenses a bundler to drop a module nothing\n * imports a value from, and a document names a node type by string — so\n * `import \"./graph2d\"` for its side effect is exactly what gets shaken out, where an\n * array of bindings is a data dependency that cannot be.\n */\nexport const NODES = [Graph2D, Graph3D];\n"]}
package/package.json CHANGED
@@ -1,6 +1,71 @@
1
1
  {
2
2
  "name": "@motionscript/plot",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0-alpha.3",
4
+ "type": "module",
5
+ "description": "Animated function plots for Motion Script: a cartesian plane and a 3D surface, driven by an expression language.",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/motionscript-dev/motionscript.git",
9
+ "directory": "packages/components/plot"
10
+ },
11
+ "homepage": "https://motionscript.dev",
12
+ "bugs": {
13
+ "url": "https://github.com/motionscript-dev/motionscript/issues"
14
+ },
15
+ "main": "dist/index.js",
16
+ "types": "dist/index.d.ts",
17
+ "sideEffects": false,
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "registry.json",
22
+ "CHANGELOG.md",
23
+ "!dist/**/*.tsbuildinfo",
24
+ "!src/**/tests/**",
25
+ "!src/**/*.test.ts",
26
+ "!src/**/*.test.tsx",
27
+ "!src/**/*.fixtures.ts"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ },
37
+ "./kit": {
38
+ "types": "./dist/engine.d.ts",
39
+ "default": "./dist/engine.js"
40
+ }
41
+ },
42
+ "keywords": [
43
+ "motion-script",
44
+ "motion script",
45
+ "plot",
46
+ "animation"
47
+ ],
48
+ "license": "Apache-2.0",
49
+ "peerDependencies": {
50
+ "@motionscript/sdk": "^0.1.0-alpha.3"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^25.6.2",
54
+ "eslint": "^9.39.4",
55
+ "eslint-import-resolver-typescript": "^4.4.4",
56
+ "eslint-plugin-import-x": "^4.16.1",
57
+ "tsc-alias": "^1.8.16",
58
+ "typescript": "^6.0.3",
59
+ "typescript-eslint": "^8.48.0",
60
+ "vitest": "^4.1.5",
61
+ "@motionscript/sdk": "0.1.0-alpha.3"
62
+ },
63
+ "scripts": {
64
+ "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json --resolve-full-paths && node ../../../scripts/build-browser.mjs",
65
+ "dev": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --resolve-full-paths -w",
66
+ "clean": "rimraf --glob dist .turbo *.tsbuildinfo",
67
+ "test": "vitest --passWithNoTests",
68
+ "lint": "eslint .",
69
+ "typecheck": "tsc -p tsconfig.json --noEmit"
70
+ }
6
71
  }
package/registry.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "kit": {
3
+ "barrel": "src/engine.ts",
4
+ "specifier": "@motionscript/plot/kit"
5
+ },
6
+ "items": [
7
+ {
8
+ "name": "graph2d",
9
+ "description": "A cartesian plane plotting one or more functions.",
10
+ "root": "src/graph2d",
11
+ "files": [
12
+ "curve-cache.ts",
13
+ "curve-error.ts",
14
+ "curve.ts",
15
+ "graph2d.ts",
16
+ "index.ts",
17
+ "plane-fill.ts",
18
+ "plane.ts",
19
+ "shared.ts"
20
+ ]
21
+ },
22
+ {
23
+ "name": "graph3d",
24
+ "description": "A 3D surface plotting z = f(x, y).",
25
+ "root": "src/graph3d",
26
+ "files": [
27
+ "expression.ts",
28
+ "graph3d.ts",
29
+ "index.ts",
30
+ "shared.ts"
31
+ ]
32
+ }
33
+ ]
34
+ }
package/src/engine.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The plot **kit**: what these components share, and what a copied one does
3
+ * not copy.
4
+ *
5
+ * `ms add` writes a component's own files into your project and rewrites its
6
+ * reach for these to `@motionscript/plot`, so the shared parts stay a real
7
+ * dependency. That is not a size decision. The engine keys signal machinery to
8
+ * class identity — `@property` installs against the class it was applied with,
9
+ * and the renderer asks `instanceof` about what it is drawing — so a second copy
10
+ * of a shared class is two classes answering one name, and whichever one your
11
+ * component did not get draws nothing at all, with no error.
12
+ */
13
+ export * from "./kit";
@@ -0,0 +1,103 @@
1
+ /**
2
+ * One frame's worth of sampled curves, kept for the next one.
3
+ *
4
+ * `renderSelf` runs every frame; between two frames the thing that has normally
5
+ * changed is nothing at all — the playhead moved through a stretch where this
6
+ * node holds still, or some other node redrew and took the whole scene with it.
7
+ * Re-running the sampler for a picture whose curves have not moved is the single
8
+ * largest avoidable cost in the node.
9
+ *
10
+ * ## Returning the *same arrays* is half the point
11
+ *
12
+ * `skia-render` keys its geometry cache by node and shape index and compares the
13
+ * new descriptor against the old with a shallow walk that short-circuits on
14
+ * reference identity. Handing back a fresh array of identical points would still
15
+ * miss, rebuild the wasm path, and pay for the walk that discovered it needn't
16
+ * have. Handing back the same array instance skips both.
17
+ *
18
+ * ## Two keys, of deliberately different kinds
19
+ *
20
+ * The **plane** is compared structurally: eight numbers, so a pan of a hundredth
21
+ * of a pixel is a miss and a redraw of an untouched frame is a hit.
22
+ *
23
+ * The **function** is compared by *identity*, which `compileCurveCached` is what
24
+ * makes meaningful — equal source is the identical closure, so an inspector
25
+ * keystroke that doesn't change the expression re-samples nothing. And a curve
26
+ * caught mid-tween is a fresh closure on every frame by design (`blendCurve`),
27
+ * so it correctly misses: it really is changing.
28
+ *
29
+ * Colour and opacity are deliberately *not* in the key. They live on the paint,
30
+ * not on the shape, so dragging a curve's colour must not throw its geometry
31
+ * away.
32
+ */
33
+
34
+ import type { Vector2 } from "@motionscript/sdk"
35
+
36
+ import {
37
+ sampleCurve,
38
+ type CurveFunction,
39
+ type PixelRect,
40
+ type PlaneMap,
41
+ type SampleBudget,
42
+ } from "./curve"
43
+
44
+ /** Everything about the view that decides where a curve's points come out. */
45
+ export function runsKeyOf(map: PlaneMap, clip: PixelRect): string {
46
+ return `${map.centerX}|${map.centerY}|${map.scaleX}|${map.scaleY}|${clip.left}|${clip.right}|${clip.bottom}|${clip.top}`
47
+ }
48
+
49
+ interface Entry {
50
+ key: string
51
+ sample: CurveFunction
52
+ runs: Vector2[][]
53
+ }
54
+
55
+ /** The sampled runs of every curve on one node, held across frames. */
56
+ export class CurveRunsCache {
57
+ private readonly entries = new Map<string, Entry>()
58
+
59
+ /**
60
+ * Whether {@link runs} would answer from memory rather than re-sample.
61
+ *
62
+ * Asked of every curve *before* any of them is sampled, so the frame's
63
+ * evaluation budget can be divided among the ones that actually have work to
64
+ * do — five cached curves and one that moved gives the whole budget to the
65
+ * one that moved.
66
+ */
67
+ holds(id: string, sample: CurveFunction, key: string): boolean {
68
+ const hit = this.entries.get(id)
69
+ return hit !== undefined && hit.key === key && hit.sample === sample
70
+ }
71
+
72
+ /** This curve's polylines, sampled only if something that decides them moved. */
73
+ runs(
74
+ id: string,
75
+ sample: CurveFunction,
76
+ key: string,
77
+ map: PlaneMap,
78
+ clip: PixelRect,
79
+ budget?: SampleBudget
80
+ ): Vector2[][] {
81
+ const hit = this.entries.get(id)
82
+ if (hit !== undefined && hit.key === key && hit.sample === sample) {
83
+ return hit.runs
84
+ }
85
+ const runs = sampleCurve(sample, map, clip, budget)
86
+ this.entries.set(id, { key, sample, runs })
87
+ return runs
88
+ }
89
+
90
+ /**
91
+ * Forgets every curve not in `ids` — the ones deleted, switched off, or faded
92
+ * to nothing since the last frame.
93
+ *
94
+ * Without it a node whose equation list is edited a hundred times holds a
95
+ * hundred stale point lists, none of which anything will ever ask for again.
96
+ */
97
+ retain(ids: readonly string[]): void {
98
+ if (this.entries.size === ids.length) return
99
+ for (const id of this.entries.keys()) {
100
+ if (!ids.includes(id)) this.entries.delete(id)
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Why an expression won't compile as `y = f(x)`, in the terms the tile shows.
3
+ *
4
+ * A one-line binding of the shared checker to this node's single free variable,
5
+ * and it is worth its own name for what it *rejects*: `x + y` is a perfectly
6
+ * good surface and a mistake on a curve, and the tile has to say so rather than
7
+ * silently plotting `x`. The 3D graph's `expressionError` is the same function
8
+ * bound the other way.
9
+ */
10
+
11
+ import {
12
+ CURVE_VARIABLES,
13
+ expressionError,
14
+ } from "../kit/expression"
15
+
16
+ /** `null` when `source` compiles as `y = f(x)`, else the reason it doesn't. */
17
+ export function curveError(source: string): string | null {
18
+ return expressionError(source, CURVE_VARIABLES)
19
+ }