@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":"graph2d.js","sourceRoot":"","sources":["../../src/graph2d/graph2d.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n node,\n Clip,\n Fills,\n Graphics2D,\n Node2D,\n clamp,\n lerpNumber,\n property,\n type Color,\n type Node2DProps,\n type NormalizedColor,\n type RenderContext2D,\n} from \"@motionscript/sdk\"\n\nimport { sampleBudget, type PixelRect } from \"./curve\"\nimport { CurveRunsCache, runsKeyOf } from \"./curve-cache\"\nimport {\n formatTick,\n inflate,\n labelLadder,\n labelWidthEm,\n planeBox,\n resolvePlane,\n tickLadder,\n toPx,\n toPy,\n type PlaneView,\n type ResolvedPlane,\n type TickLadder,\n} from \"./plane\"\nimport {\n PLANE_SOURCE,\n planeUniforms,\n planeVisible,\n type PlaneStyle,\n} from \"./plane-fill\"\nimport {\n compileCurveCached,\n lerpColor,\n lerpCurves,\n resolveColor,\n snapFlag,\n snapValue,\n type CurveResolved,\n} from \"./shared\"\n\n// --- Authored props --------------------------------------------------------\n// The loose shapes a caller writes. Each has a matching `*Resolved` type below:\n// what the `@property` mapper turns it into and what the tween interpolates.\n\n/** One plotted curve, `y = f(x)`. */\nexport interface Graph2DEquation {\n /** Identity across list changes — see {@link Graph2D.equations}. */\n id: string | number\n /** The expression, evaluated as `y = f(x)` (e.g. `sin(x)`, `log(x)`). */\n expression: string\n /** The curve's colour: any {@link Color} — a CSS string or an RGBA tuple. */\n color: Color\n /** Whether the curve is drawn. Defaults to true. */\n visible?: boolean\n /** How opaque the curve is, 0–1. Defaults to 1. */\n opacity?: number\n}\n\n/** The paper the curves are drawn on: the grid, the axes and their numbers. */\nexport interface Graph2DGrid {\n /** Whether to rule the plane at all. Defaults to true. */\n showGrid?: boolean\n /** Whether to draw the faint lines between the numbered ones. Defaults to true. */\n showMinorGrid?: boolean\n /** Whether to draw the two lines through the origin. Defaults to true. */\n showAxes?: boolean\n /** Whether to number the axes. Defaults to true. */\n showLabels?: boolean\n colorGrid?: Color\n colorMinorGrid?: Color\n colorAxis?: Color\n colorLabel?: Color\n /** Width of a grid line. The minor lines are drawn at two thirds of it. */\n lineWidth?: number\n /** Width of the two axis lines. */\n axisWidth?: number\n /** Size of the axis numbers. Also sets how close two of them may sit. */\n fontSize?: number\n /** Family the axis numbers are set in. Empty takes the built-in face. */\n fontFamily?: string\n}\n\nexport interface Graph2DProps extends Node2DProps {\n /** The curves to plot, each evaluated as `y = f(x)` across the visible range. */\n equations: Graph2DEquation[]\n /** Graph x at the centre of the box — see {@link PlaneView}. */\n centerX: number\n /** Graph y at the centre of the box. */\n centerY: number\n /** Width of the visible window, in graph units. */\n xSpan: number\n /**\n * Height of the visible window in graph units, or **0 to keep units square**,\n * which is the default and almost always what you want. See {@link PlaneView}.\n */\n ySpan: number\n grid: Graph2DGrid\n /** The paper's colour. */\n background: Color\n /** How opaque the paper is, 0–1. At 0 the graph draws over whatever is behind it. */\n backgroundOpacity: number\n /** How thick a plotted curve is drawn. */\n lineWidth: number\n}\n\n// --- Resolved props --------------------------------------------------------\n// Canonical, fully defaulted, interpolatable. Nothing here is optional, so the\n// draw never re-derives a default and the tweens never test for one.\n\n/** {@link Graph2DGrid} after {@link resolveGrid}. */\ninterface GridResolved {\n showGrid: boolean\n showMinorGrid: boolean\n showAxes: boolean\n showLabels: boolean\n colorGrid: NormalizedColor\n colorMinorGrid: NormalizedColor\n colorAxis: NormalizedColor\n colorLabel: NormalizedColor\n lineWidth: number\n axisWidth: number\n fontSize: number\n fontFamily: string\n}\n\n/**\n * The grid a node with nothing said about it draws — the same dark palette the\n * 3D graph opens on, so the two read as one family when they share a scene.\n */\nconst DEFAULT_GRID: GridResolved = {\n showGrid: true,\n showMinorGrid: true,\n showAxes: true,\n showLabels: true,\n colorGrid: resolveColor(\"#374151\"),\n colorMinorGrid: resolveColor(\"#1f2937\"),\n colorAxis: resolveColor(\"#6b7280\"),\n colorLabel: resolveColor(\"#9ca3af\"),\n lineWidth: 2,\n axisWidth: 3,\n fontSize: 22,\n fontFamily: \"\",\n}\n\n/** How far a curve may run past the box before it is cut away, in stroke widths. */\nconst CLIP_MARGIN = 1\n\n/**\n * How close two stacked numbers may sit before the ladder steps up, in font\n * sizes.\n *\n * Only the vertical one lives here. Across, how close two numbers may sit\n * depends on how *long* they are — five characters near the origin, fourteen at\n * a deep zoom — so that ladder is built by `labelLadder`, which measures what it\n * just produced and widens itself once if it has to. Down, a number is one line\n * whatever it says.\n */\nconst LABEL_PITCH_Y = 2.6\nconst LABEL_GAP = 0.45\n/**\n * Narrowest the box a number is aligned within may be, in font sizes.\n *\n * Deliberately wider than the numbers an ordinary view carries, so a\n * right-aligned `-1200` and a right-aligned `5` end in the same place. It only\n * ever grows from here — see `labelWidthEm`.\n */\nconst LABEL_BOX = 6\n\n/**\n * A 2D function grapher — any number of `y = f(x)` curves over a ruled plane,\n * with numbered axes.\n *\n * <Graph2D\n * width={960} height={540}\n * equations={[{ id: 1, expression: \"log(x)\", color: \"#3B82F6\" }]}\n * />\n *\n * The sibling of {@link Graph3D}, and deliberately built the same way: the same\n * little expression language, the same equation tiles, the same match-by-id\n * tween over the list. What it does *not* share is the hard part, which is not\n * the maths but the drawing — see `curve.ts`, which exists because the obvious\n * sampling loop cuts `log(x)` off in mid-air, silently drops `1/x` entirely on a\n * fixed-point rasterizer, and puts a visible polygonal kink wherever a curve\n * turns sharply.\n *\n * Every prop is a `@property`, so a constant, a `() => signal()` binding, and\n * `set()`/`to()` on a ref all work and agree. The non-scalar props each declare\n * a `mapper` (loose authored shape → one canonical internal shape) and a `tween`\n * (how that shape interpolates) — without the tween, `to({ grid: … })` would hold\n * the old value and snap at the end, which is the standard trap for an\n * object-valued attribute.\n *\n * **The view is four plain numbers, not a transform.** `centerX`/`centerY` and\n * `xSpan`/`ySpan` are each independently tweenable, which is what makes \"pan\n * across while zooming in\" one command and what keeps every frame reproducible\n * under scrubbing and export. The canvas's pan/zoom gesture writes exactly these\n * four, so a view flown by hand is a view a `to` can animate afterwards.\n */\n@node({\n key: \"graph2d\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n acceptsChildren: true,\n },\n seed: {\n width: 960,\n height: 540,\n },\n})\nexport class Graph2D extends Node2D<Graph2DProps> {\n /**\n * The plotted curves.\n *\n * The mapper compiles each expression **once per write** (memoised by source),\n * so the draw — which re-runs every frame — never re-parses; and it folds\n * `visible`/`opacity` into a single number, so hiding is expressible as a\n * fade. An expression that doesn't parse is dropped and the rest still draw,\n * which is what lets the inspector commit on every keystroke.\n *\n * The tween matches the two lists by `id` — see `lerpCurves`.\n */\n @property({ default: [], mapper: resolveEquations, tween: lerpCurves })\n declare equations: Graph2DEquation[]\n\n @property({ default: 0 }) declare centerX: number\n @property({ default: 0 }) declare centerY: number\n @property({ default: 20 }) declare xSpan: number\n @property({ default: 0 }) declare ySpan: number\n\n /** The paper. Flags snap at the end of a tween; the rest lerp. */\n @property({ default: {}, mapper: resolveGrid, tween: lerpGrid })\n declare grid: Graph2DGrid\n\n @property({ default: \"#111827\", mapper: resolveColor, tween: lerpColor })\n declare background: Color\n @property({ default: 1 }) declare backgroundOpacity: number\n @property({ default: 5 }) declare lineWidth: number\n\n /**\n * The node's one piece of memoisation, and it earns its keep: a graph that is\n * holding still re-samples nothing at all. See `curve-cache.ts`.\n */\n private readonly runs = new CurveRunsCache()\n\n /** Where the node is looking, as the four props hold it. */\n private get view(): PlaneView {\n return {\n centerX: this.centerX,\n centerY: this.centerY,\n xSpan: this.xSpan,\n ySpan: this.ySpan,\n }\n }\n\n /**\n * Confines every mark to the node's own box — its own drawing as well as its\n * children's, which is why this is `clipPathSelf` and not `clipSelf`.\n *\n * Belt and braces over the geometric clipping in `curve.ts` rather than a\n * substitute for it: that clipping is what keeps astronomical coordinates away\n * from the rasterizer, and this is what stops a stroke's half-width and its\n * round joins from painting a hair outside the frame at the edges.\n */\n protected override clipPathSelf(): Clip | null {\n const width = this.layoutBounds.width\n const height = this.layoutBounds.height\n if (width <= 0 || height <= 0) return null\n return new Clip().rect({ x: 0, y: 0, width, height })\n }\n\n // ---- Drawing ------------------------------------------------------------\n\n protected override renderSelf(ctx: RenderContext2D): void {\n const width = this.layoutBounds.width\n const height = this.layoutBounds.height\n if (width <= 0 || height <= 0) return\n\n const plane = resolvePlane(this.view, width, height)\n const grid = this.grid as unknown as GridResolved\n const box = planeBox(plane)\n\n // One ladder per axis, chosen from the *scale* rather than from a fixed\n // count: what decides how finely the plane is ruled is how close two numbers\n // would sit on screen, which is the only thing that stays true as the view is\n // flown around.\n const xTicks = labelLadder(plane.xMin, plane.xMax, plane.scaleX, grid.fontSize)\n const yTicks = tickLadder(\n plane.yMin,\n plane.yMax,\n plane.scaleY,\n grid.fontSize * LABEL_PITCH_Y\n )\n\n this.drawPlane(ctx, plane, grid, xTicks, yTicks)\n if (grid.showLabels) this.drawLabels(ctx, plane, box, grid, xTicks, yTicks)\n\n this.drawCurves(ctx, plane, box)\n }\n\n /**\n * The paper, the two orders of ruling and the two axes — one rect, one shader,\n * one draw, whatever the zoom.\n *\n * This was six hundred filled rects and three draws, and the rect count is the\n * part that mattered: it changed as the ladder stepped, and the renderer keys\n * its geometry cache by shape *index*, so a grid that gained a line invalidated\n * every curve behind it. One shape means the curves' keys never move. See\n * `plane-fill.ts` for why the built-in grid fill can't do this job and for what\n * the phase uniforms are protecting against.\n */\n private drawPlane(\n ctx: RenderContext2D,\n plane: ResolvedPlane,\n grid: GridResolved,\n xTicks: TickLadder,\n yTicks: TickLadder\n ): void {\n const style: PlaneStyle = {\n paper: this.background as NormalizedColor,\n paperOpacity: clamp01(this.backgroundOpacity),\n showGrid: grid.showGrid,\n showMinorGrid: grid.showMinorGrid,\n showAxes: grid.showAxes,\n colorGrid: grid.colorGrid,\n colorMinorGrid: grid.colorMinorGrid,\n colorAxis: grid.colorAxis,\n lineWidth: grid.lineWidth,\n axisWidth: grid.axisWidth,\n }\n // Nothing to paint at all — clear paper, no rulings, no axes — is a whole\n // fragment pass over the box that would come out transparent.\n if (!planeVisible(style)) return\n\n ctx.draw(\n new Graphics2D()\n .rect({ x: 0, y: 0, width: plane.width, height: plane.height })\n .fill(\n Fills.shader(PLANE_SOURCE, {\n uniforms: planeUniforms(plane, style, xTicks, yTicks),\n coords: \"local\",\n })\n )\n )\n }\n\n /**\n * The numbers along each axis.\n *\n * Pinned to the frame when their axis has left it, which is the behaviour\n * every map and every grapher has and the reason it is worth the arithmetic:\n * pan far enough that the origin is off screen and a graph whose numbers went\n * with it is a graph you can no longer read. The row also flips to the other\n * side of its axis when there is no room on the near one, so a view sitting\n * just above the x-axis still numbers it.\n */\n private drawLabels(\n ctx: RenderContext2D,\n plane: ResolvedPlane,\n box: PixelRect,\n grid: GridResolved,\n xTicks: TickLadder,\n yTicks: TickLadder\n ): void {\n const labels = new Graphics2D()\n const gap = grid.fontSize * LABEL_GAP\n // Per axis, because the two ladders carry different numbers: a view can be\n // ten units wide and a billionth of one tall.\n const xBox = grid.fontSize * Math.max(LABEL_BOX, labelWidthEm(xTicks))\n const yBox = grid.fontSize * Math.max(LABEL_BOX, labelWidthEm(yTicks))\n const font = {\n fontSize: grid.fontSize,\n // An empty family is dropped rather than passed on: the renderer's font\n // provider holds only what the manifest registered, so asking it for a\n // family called `\"\"` shapes against nothing and paints no glyphs. Left\n // unset it inherits the scene's own default face.\n ...(grid.fontFamily === \"\" ? {} : { fontFamily: grid.fontFamily }),\n wrap: false,\n }\n\n // The x-axis row: under the axis when there is room beneath it, over it when\n // there isn't.\n const axisY = clamp(toPy(plane, 0), box.bottom, box.top)\n const under = axisY - gap - grid.fontSize >= box.bottom\n const rowY = under ? axisY - gap - grid.fontSize / 2 : axisY + gap + grid.fontSize / 2\n\n for (const x of xTicks.major) {\n labels.text({\n ...font,\n text: formatTick(x, xTicks.step),\n x: toPx(plane, x),\n y: rowY,\n width: xBox,\n textAlign: \"center\",\n })\n }\n\n // The y-axis column: to the left of the axis when there is room, to the\n // right when the axis is against the left edge.\n // Room for a couple of characters, not for the whole alignment box: the box\n // is deliberately wide (a right-aligned `-1200` and a right-aligned `5` end\n // in the same place), and demanding all of it would push the column to the\n // wrong side of an axis that has plenty of room for the numbers it holds.\n const axisX = clamp(toPx(plane, 0), box.left, box.right)\n const leftOf = axisX - gap - grid.fontSize * 1.5 >= box.left\n const columnX = leftOf ? axisX - gap - yBox / 2 : axisX + gap + yBox / 2\n\n // Zero belongs to the row, not the column: drawn in both it would be two\n // numbers stacked on the origin. Skipped only when the row is actually\n // showing it, so a view with the y-axis off screen still numbers its own 0.\n const rowHasZero = plane.xMin <= 0 && 0 <= plane.xMax\n\n for (const y of yTicks.major) {\n if (y === 0 && rowHasZero) continue\n labels.text({\n ...font,\n text: formatTick(y, yTicks.step),\n x: columnX,\n y: toPy(plane, y),\n width: yBox,\n textAlign: leftOf ? \"right\" : \"left\",\n })\n }\n\n ctx.draw(labels.fill(Fills.color(grid.colorLabel)))\n }\n\n /**\n * The curves themselves, one stroked polyline per run.\n *\n * One `draw` per run rather than one per curve, and that is not an\n * optimisation to undo: shapes chained before a `stroke()` are combined into a\n * single surface, and stroking a union of disjoint open paths paints nothing —\n * so a curve that leaves the frame and comes back would lose *both* halves.\n * `ChartLine` learned the same thing about its spotlight runs.\n */\n private drawCurves(\n ctx: RenderContext2D,\n plane: ResolvedPlane,\n box: PixelRect\n ): void {\n const equations = this.equations as unknown as CurveResolved[]\n const weight = Math.max(0.1, this.lineWidth)\n const clip = inflate(box, weight * CLIP_MARGIN)\n const key = runsKeyOf(plane, clip)\n\n // What actually has to be sampled, counted before any of it is — so a frame\n // where five of six curves are unchanged hands the whole evaluation budget\n // to the one that moved rather than reserving five sixths of it for work\n // that is already done.\n const live: string[] = []\n let misses = 0\n for (const equation of equations) {\n // Fully faded out (hidden, or the far end of a cross-fade): sample nothing.\n if (equation.opacity <= 0.001) continue\n live.push(equation.id)\n if (!this.runs.holds(equation.id, equation.sample, key)) misses++\n }\n const budget = misses > 0 ? sampleBudget(misses) : undefined\n this.runs.retain(live)\n\n for (const equation of equations) {\n if (equation.opacity <= 0.001) continue\n\n const stroke = {\n weight,\n fill: Fills.color(equation.color, {\n opacity: clamp01(equation.opacity),\n }),\n // Round on both counts because this is a *curve*: a miter join on a\n // sharp turn throws a spike, and a butt cap leaves a visible flat end\n // wherever a run stops at a domain edge.\n cap: \"round\" as const,\n join: \"round\" as const,\n }\n\n const runs = this.runs.runs(\n equation.id,\n equation.sample,\n key,\n plane,\n clip,\n budget\n )\n for (const run of runs) {\n if (run.length < 2) continue\n ctx.draw(new Graphics2D().line({ points: run, radius: 0 }).stroke(stroke))\n }\n }\n }\n}\n\n// --- Mappers & tweens ------------------------------------------------------\n\n/** Mapper for {@link Graph2D.equations}. */\nfunction resolveEquations(\n input: Graph2DEquation[] | undefined\n): CurveResolved[] {\n if (!input) return []\n const out: CurveResolved[] = []\n for (const equation of input) {\n const sample = compileCurveCached(equation.expression)\n if (!sample) continue // unparseable or empty — drop it, keep the rest\n out.push({\n id: String(equation.id),\n sample,\n color: resolveColor(equation.color),\n opacity: equation.visible === false ? 0 : (equation.opacity ?? 1),\n })\n }\n return out\n}\n\n/**\n * Mapper for {@link Graph2D.grid}.\n *\n * Merges onto `previous` rather than onto the bare defaults, which is what makes\n * a partial write mean \"change this much and leave the rest\". It matters most on\n * a `to` command, whose target states only the fields that command touches: a\n * tween that dims the grid must not silently hand the axis numbers back to their\n * default size on the way.\n */\nfunction resolveGrid(\n input: Graph2DGrid | undefined,\n previous?: GridResolved\n): GridResolved {\n const base = previous ?? DEFAULT_GRID\n return {\n showGrid: input?.showGrid ?? base.showGrid,\n showMinorGrid: input?.showMinorGrid ?? base.showMinorGrid,\n showAxes: input?.showAxes ?? base.showAxes,\n showLabels: input?.showLabels ?? base.showLabels,\n colorGrid: colorOr(input?.colorGrid, base.colorGrid),\n colorMinorGrid: colorOr(input?.colorMinorGrid, base.colorMinorGrid),\n colorAxis: colorOr(input?.colorAxis, base.colorAxis),\n colorLabel: colorOr(input?.colorLabel, base.colorLabel),\n lineWidth: input?.lineWidth ?? base.lineWidth,\n axisWidth: input?.axisWidth ?? base.axisWidth,\n fontSize: input?.fontSize ?? base.fontSize,\n fontFamily: input?.fontFamily ?? base.fontFamily,\n }\n}\n\nfunction colorOr(\n input: Color | undefined,\n fallback: NormalizedColor\n): NormalizedColor {\n return input === undefined ? fallback : resolveColor(input)\n}\n\n/** Tween for {@link Graph2D.grid}. */\nfunction lerpGrid(\n from: GridResolved,\n to: GridResolved,\n t: number\n): GridResolved {\n return {\n showGrid: snapFlag(from.showGrid, to.showGrid, t),\n showMinorGrid: snapFlag(from.showMinorGrid, to.showMinorGrid, t),\n showAxes: snapFlag(from.showAxes, to.showAxes, t),\n showLabels: snapFlag(from.showLabels, to.showLabels, t),\n colorGrid: lerpColor(from.colorGrid, to.colorGrid, t),\n colorMinorGrid: lerpColor(from.colorMinorGrid, to.colorMinorGrid, t),\n colorAxis: lerpColor(from.colorAxis, to.colorAxis, t),\n colorLabel: lerpColor(from.colorLabel, to.colorLabel, t),\n lineWidth: lerpNumber(from.lineWidth, to.lineWidth, t),\n axisWidth: lerpNumber(from.axisWidth, to.axisWidth, t),\n fontSize: lerpNumber(from.fontSize, to.fontSize, t),\n // A face has no halfway point, so it changes once the tween lands.\n fontFamily: snapValue(from.fontFamily, to.fontFamily, t),\n }\n}\n\nfunction clamp01(value: number): number {\n return Number.isFinite(value) ? clamp(value, 0, 1) : 1\n}\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The Graph 2D node — a **native** node type (see `node-types.ts` /
3
+ * `NODE_CLASS_BY_KEY`) like the 3D graph beside it: built, themed and animated
4
+ * from the inspector rather than loaded as a code package.
5
+ *
6
+ * It plots any number of `y = f(x)` curves over a ruled plane, sampling each
7
+ * from an expression the author types. The language is shared with the 3D graph
8
+ * (`nodes/graph-kit/expression`) and lives in this package rather than in a maths
9
+ * library for the same two reasons: an expression is a value stored in a scene
10
+ * document, so compiling it as JavaScript would make every scene file an
11
+ * execution vector; and a curve is a few thousand evaluations per frame, which a
12
+ * general evaluator would not survive.
13
+ *
14
+ * What is genuinely this node's own is `curve.ts`. Turning `y = f(x)` into
15
+ * polylines is where a grapher is actually won or lost — the naive loop cuts
16
+ * `log(x)` off in mid-air, silently drops `1/x` on a fixed-point rasterizer, and
17
+ * kinks visibly wherever a curve turns — and that module is the four fixes for
18
+ * it, written out with the reasoning.
19
+ *
20
+ * Unlike the 3D graph there *is* a pointer here: double-clicking the node opens a
21
+ * pan/zoom session on the canvas. It stays reproducible because what the gesture
22
+ * writes is the same four plain numbers a `to` command animates — see
23
+ * `plane-camera.ts`, which holds the arithmetic and no DOM.
24
+ */
25
+ export * from "./curve.js";
26
+ export * from "./curve-cache.js";
27
+ export * from "./curve-error.js";
28
+ export * from "./graph2d.js";
29
+ export * from "./plane.js";
30
+ export * from "./plane-fill.js";
31
+ export * from "./shared.js";
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/graph2d/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The Graph 2D node — a **native** node type (see `node-types.ts` /
3
+ * `NODE_CLASS_BY_KEY`) like the 3D graph beside it: built, themed and animated
4
+ * from the inspector rather than loaded as a code package.
5
+ *
6
+ * It plots any number of `y = f(x)` curves over a ruled plane, sampling each
7
+ * from an expression the author types. The language is shared with the 3D graph
8
+ * (`nodes/graph-kit/expression`) and lives in this package rather than in a maths
9
+ * library for the same two reasons: an expression is a value stored in a scene
10
+ * document, so compiling it as JavaScript would make every scene file an
11
+ * execution vector; and a curve is a few thousand evaluations per frame, which a
12
+ * general evaluator would not survive.
13
+ *
14
+ * What is genuinely this node's own is `curve.ts`. Turning `y = f(x)` into
15
+ * polylines is where a grapher is actually won or lost — the naive loop cuts
16
+ * `log(x)` off in mid-air, silently drops `1/x` on a fixed-point rasterizer, and
17
+ * kinks visibly wherever a curve turns — and that module is the four fixes for
18
+ * it, written out with the reasoning.
19
+ *
20
+ * Unlike the 3D graph there *is* a pointer here: double-clicking the node opens a
21
+ * pan/zoom session on the canvas. It stays reproducible because what the gesture
22
+ * writes is the same four plain numbers a `to` command animates — see
23
+ * `plane-camera.ts`, which holds the arithmetic and no DOM.
24
+ */
25
+ export * from "./curve.js";
26
+ export * from "./curve-cache.js";
27
+ export * from "./curve-error.js";
28
+ export * from "./graph2d.js";
29
+ export * from "./plane.js";
30
+ export * from "./plane-fill.js";
31
+ export * from "./shared.js";
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/graph2d/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC","sourcesContent":["/**\n * The Graph 2D node — a **native** node type (see `node-types.ts` /\n * `NODE_CLASS_BY_KEY`) like the 3D graph beside it: built, themed and animated\n * from the inspector rather than loaded as a code package.\n *\n * It plots any number of `y = f(x)` curves over a ruled plane, sampling each\n * from an expression the author types. The language is shared with the 3D graph\n * (`nodes/graph-kit/expression`) and lives in this package rather than in a maths\n * library for the same two reasons: an expression is a value stored in a scene\n * document, so compiling it as JavaScript would make every scene file an\n * execution vector; and a curve is a few thousand evaluations per frame, which a\n * general evaluator would not survive.\n *\n * What is genuinely this node's own is `curve.ts`. Turning `y = f(x)` into\n * polylines is where a grapher is actually won or lost — the naive loop cuts\n * `log(x)` off in mid-air, silently drops `1/x` on a fixed-point rasterizer, and\n * kinks visibly wherever a curve turns — and that module is the four fixes for\n * it, written out with the reasoning.\n *\n * Unlike the 3D graph there *is* a pointer here: double-clicking the node opens a\n * pan/zoom session on the canvas. It stays reproducible because what the gesture\n * writes is the same four plain numbers a `to` command animates — see\n * `plane-camera.ts`, which holds the arithmetic and no DOM.\n */\nexport * from \"./curve\";\nexport * from \"./curve-cache\";\nexport * from \"./curve-error\";\nexport * from \"./graph2d\";\nexport * from \"./plane\";\nexport * from \"./plane-fill\";\nexport * from \"./shared\";\n"]}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * The ruled plane as a **paint** rather than as geometry.
3
+ *
4
+ * The grid used to be one filled rect per line — up to four hundred per axis per
5
+ * order, chained into one `Graphics` so at least the *draws* stayed constant.
6
+ * That is the arrangement `ChartCanvas` makes, and for a chart it is right: a
7
+ * chart's axes are ruled once and then sit still. A grapher's are not. Every
8
+ * rect moves on every frame of a pan, so the renderer's shape cache never hits;
9
+ * worse, the rect *count* changes as the ladder steps, and the cache is keyed by
10
+ * shape index, so a grid that gained a line shifted the key of every curve drawn
11
+ * after it and threw those away too.
12
+ *
13
+ * A shader has none of that. The plane is one shape whatever the zoom, so the
14
+ * curves' cache keys are stable, the wasm path behind it is built once, and the
15
+ * cost of ruling the plane stops depending on how much of it is ruled — which is
16
+ * the whole reason the old {@link MAX_TICKS} cap existed.
17
+ *
18
+ * ## Why not the built-in `grid` fill
19
+ *
20
+ * `Fills.grid` measures its pitch in *divisions of the shape* — deliberately, so
21
+ * a grid of eight stays a grid of eight when the shape resizes. A graph's pitch
22
+ * is decided by the tick ladder and is a different number of pixels on each
23
+ * axis, which one scalar `divisions` cannot say; and it carries one colour,
24
+ * where a graph has two orders in two colours plus its axes.
25
+ *
26
+ * ## Everything variable is a uniform
27
+ *
28
+ * `getOrCompileSkSL` caches by *source string* and never evicts, so a source
29
+ * built per frame is a new program per frame. {@link PLANE_SOURCE} is therefore a
30
+ * module constant and every switch — the two orders, the axes, the paper — is
31
+ * expressed by a uniform that can go to zero.
32
+ *
33
+ * ## The phase is the whole trick
34
+ *
35
+ * Uniforms reach the shader as **fp32**. Handing it "where x = 0 is" would be
36
+ * fine at the default zoom and 1e15 pixels at a deep one off the origin, which
37
+ * is not a number fp32 holds at all. So {@link planeUniforms} does the reduction
38
+ * in double precision on the way in: it splits the centre into a whole number of
39
+ * steps plus a remainder *before* anything is multiplied by the scale, and hands
40
+ * over a phase inside a single cell. Every number the shader sees is bounded by
41
+ * the node's own box.
42
+ */
43
+ import type { NormalizedColor } from "@motionscript/sdk";
44
+ import type { ResolvedPlane, TickLadder } from "./plane.js";
45
+ /**
46
+ * The plane's paint program.
47
+ *
48
+ * Coverage-based rather than a hard test, and the ramp is one *device* pixel
49
+ * wide (`1 / u_scale`, the idiom every built-in pattern fill uses), so a ruling
50
+ * is a crisp hairline whether the camera is zoomed in on the node or the scene
51
+ * is being exported at four times scale.
52
+ *
53
+ * Uniforms are declared widest-first out of habit rather than necessity — a
54
+ * shader fill's uniforms are bound by *name* through reflection, unlike the
55
+ * built-in fills, whose flat positional array makes declaration order load
56
+ * bearing. `u_origin` and `u_scale` are supplied by the renderer.
57
+ */
58
+ export declare const PLANE_SOURCE = "\nuniform vec4 u_paper;\nuniform vec4 u_minorColor;\nuniform vec4 u_majorColor;\nuniform vec4 u_axisColor;\nuniform vec2 u_origin;\nuniform vec2 u_pitch;\nuniform vec2 u_phase;\nuniform vec2 u_divisions;\nuniform vec2 u_axisAt;\nuniform float u_majorHalf;\nuniform float u_minorHalf;\nuniform float u_axisHalf;\nuniform float u_scale;\n\n// Coverage of a line `d` px away, of half-width `w`, ramped over `feather`.\n//\n// The zero test is not a shortcut. The ramp is centred on the line, so a line of\n// no width still lands half a pixel of coverage on the pixels it runs through \u2014\n// an order you switched off, drawn faintly. Answering it here rather than in the\n// ramp is what lets one compiled program serve showGrid, showMinorGrid, showAxes\n// and a grid width of zero.\nfloat ink(float d, float w, float feather) {\n if (w <= 0.0) return 0.0;\n return clamp((w - d) / feather + 0.5, 0.0, 1.0);\n}\n\n// Distance from `t` to the nearest ruling. SkSL's mod is floor-based, so this\n// is right on either side of the phase without a sign test.\nfloat toRuling(float t, float pitch, float phase) {\n float m = mod(t - phase, pitch);\n return min(m, pitch - m);\n}\n\n// A straight colour under a coverage, premultiplied.\nvec4 lay(vec4 color, float cover) {\n float a = color.a * cover;\n return vec4(color.rgb * a, a);\n}\n\n// `src` over `dst`, both premultiplied.\nvec4 stack(vec4 src, vec4 dst) {\n return src + dst * (1.0 - src.a);\n}\n\nvec4 main(vec2 fragCoord) {\n vec2 p = fragCoord - u_origin;\n float feather = 1.0 / max(u_scale, 0.0001);\n\n // Floored well above zero: a mod by zero paints garbage rather than nothing.\n vec2 major = max(u_pitch, vec2(0.01));\n vec2 minor = major / max(u_divisions, vec2(1.0));\n\n vec2 dMajor = vec2(toRuling(p.x, major.x, u_phase.x),\n toRuling(p.y, major.y, u_phase.y));\n vec2 dMinor = vec2(toRuling(p.x, minor.x, u_phase.x),\n toRuling(p.y, minor.y, u_phase.y));\n\n float majorCover = max(ink(dMajor.x, u_majorHalf, feather),\n ink(dMajor.y, u_majorHalf, feather));\n\n // Every numbered ruling is also a faint one \u2014 the minor pitch divides the\n // major exactly \u2014 so the faint order is cut away inside the numbered one's\n // whole footprint, its ramp included. Drawing both would leave a hairline of\n // the faint colour either side of every numbered line, where the numbered\n // one's own ramp has not yet reached full coverage. The old ladder dropped\n // coincident minors from a list; a shader has no list, so it subtracts.\n float minorCover = max(\n ink(dMinor.x, u_minorHalf, feather)\n * (1.0 - ink(dMajor.x, u_majorHalf + feather, feather)),\n ink(dMinor.y, u_minorHalf, feather)\n * (1.0 - ink(dMajor.y, u_majorHalf + feather, feather)));\n\n float axisCover = max(ink(abs(p.x - u_axisAt.x), u_axisHalf, feather),\n ink(abs(p.y - u_axisAt.y), u_axisHalf, feather));\n\n vec4 c = lay(u_paper, 1.0);\n c = stack(lay(u_minorColor, minorCover), c);\n c = stack(lay(u_majorColor, majorCover), c);\n c = stack(lay(u_axisColor, axisCover), c);\n return c;\n}\n";
59
+ /** What {@link planeUniforms} needs to know about how the plane is styled. */
60
+ export interface PlaneStyle {
61
+ /** The paper's colour. */
62
+ paper: NormalizedColor;
63
+ /** How opaque the paper is, 0–1. At 0 the graph draws over what is behind it. */
64
+ paperOpacity: number;
65
+ showGrid: boolean;
66
+ showMinorGrid: boolean;
67
+ showAxes: boolean;
68
+ colorGrid: NormalizedColor;
69
+ colorMinorGrid: NormalizedColor;
70
+ colorAxis: NormalizedColor;
71
+ /** Width of a numbered ruling. The faint ones are drawn at two thirds of it. */
72
+ lineWidth: number;
73
+ /** Width of the two lines through the origin. */
74
+ axisWidth: number;
75
+ }
76
+ /**
77
+ * The uniform record {@link PLANE_SOURCE} is fed.
78
+ *
79
+ * A type alias rather than an interface so it satisfies the renderer's
80
+ * `Record<string, number | number[]>` without a cast — interfaces have no
81
+ * implicit index signature.
82
+ */
83
+ export type PlaneUniforms = {
84
+ u_paper: number[];
85
+ u_minorColor: number[];
86
+ u_majorColor: number[];
87
+ u_axisColor: number[];
88
+ u_pitch: number[];
89
+ u_phase: number[];
90
+ u_divisions: number[];
91
+ u_axisAt: number[];
92
+ u_majorHalf: number;
93
+ u_minorHalf: number;
94
+ u_axisHalf: number;
95
+ };
96
+ /**
97
+ * Whether the plane has anything to paint at all — no paper, no rulings and no
98
+ * axes is a draw the node can skip outright.
99
+ */
100
+ export declare function planeVisible(style: PlaneStyle): boolean;
101
+ /**
102
+ * The uniforms that put {@link PLANE_SOURCE} over a resolved plane.
103
+ *
104
+ * Everything handed over is bounded by the node's box — a pitch of tens of
105
+ * pixels, a phase inside one cell, an axis position inside the frame or parked
106
+ * at {@link AXIS_OFF}. That is deliberate and it is what makes the deep zoom
107
+ * work: see the module note.
108
+ */
109
+ export declare function planeUniforms(plane: ResolvedPlane, style: PlaneStyle, xTicks: TickLadder, yTicks: TickLadder): PlaneUniforms;
110
+ //# sourceMappingURL=plane-fill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plane-fill.d.ts","sourceRoot":"","sources":["../../src/graph2d/plane-fill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAExD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,+rGAkFxB,CAAA;AAED,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACzB,0BAA0B;IAC1B,KAAK,EAAE,eAAe,CAAA;IACtB,iFAAiF;IACjF,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,aAAa,EAAE,OAAO,CAAA;IACtB,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,EAAE,eAAe,CAAA;IAC1B,cAAc,EAAE,eAAe,CAAA;IAC/B,SAAS,EAAE,eAAe,CAAA;IAC1B,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAA;IACjB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAiBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIvD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,GACjB,aAAa,CAsCf"}
@@ -0,0 +1,248 @@
1
+ /**
2
+ * The ruled plane as a **paint** rather than as geometry.
3
+ *
4
+ * The grid used to be one filled rect per line — up to four hundred per axis per
5
+ * order, chained into one `Graphics` so at least the *draws* stayed constant.
6
+ * That is the arrangement `ChartCanvas` makes, and for a chart it is right: a
7
+ * chart's axes are ruled once and then sit still. A grapher's are not. Every
8
+ * rect moves on every frame of a pan, so the renderer's shape cache never hits;
9
+ * worse, the rect *count* changes as the ladder steps, and the cache is keyed by
10
+ * shape index, so a grid that gained a line shifted the key of every curve drawn
11
+ * after it and threw those away too.
12
+ *
13
+ * A shader has none of that. The plane is one shape whatever the zoom, so the
14
+ * curves' cache keys are stable, the wasm path behind it is built once, and the
15
+ * cost of ruling the plane stops depending on how much of it is ruled — which is
16
+ * the whole reason the old {@link MAX_TICKS} cap existed.
17
+ *
18
+ * ## Why not the built-in `grid` fill
19
+ *
20
+ * `Fills.grid` measures its pitch in *divisions of the shape* — deliberately, so
21
+ * a grid of eight stays a grid of eight when the shape resizes. A graph's pitch
22
+ * is decided by the tick ladder and is a different number of pixels on each
23
+ * axis, which one scalar `divisions` cannot say; and it carries one colour,
24
+ * where a graph has two orders in two colours plus its axes.
25
+ *
26
+ * ## Everything variable is a uniform
27
+ *
28
+ * `getOrCompileSkSL` caches by *source string* and never evicts, so a source
29
+ * built per frame is a new program per frame. {@link PLANE_SOURCE} is therefore a
30
+ * module constant and every switch — the two orders, the axes, the paper — is
31
+ * expressed by a uniform that can go to zero.
32
+ *
33
+ * ## The phase is the whole trick
34
+ *
35
+ * Uniforms reach the shader as **fp32**. Handing it "where x = 0 is" would be
36
+ * fine at the default zoom and 1e15 pixels at a deep one off the origin, which
37
+ * is not a number fp32 holds at all. So {@link planeUniforms} does the reduction
38
+ * in double precision on the way in: it splits the centre into a whole number of
39
+ * steps plus a remainder *before* anything is multiplied by the scale, and hands
40
+ * over a phase inside a single cell. Every number the shader sees is bounded by
41
+ * the node's own box.
42
+ */
43
+ /**
44
+ * The plane's paint program.
45
+ *
46
+ * Coverage-based rather than a hard test, and the ramp is one *device* pixel
47
+ * wide (`1 / u_scale`, the idiom every built-in pattern fill uses), so a ruling
48
+ * is a crisp hairline whether the camera is zoomed in on the node or the scene
49
+ * is being exported at four times scale.
50
+ *
51
+ * Uniforms are declared widest-first out of habit rather than necessity — a
52
+ * shader fill's uniforms are bound by *name* through reflection, unlike the
53
+ * built-in fills, whose flat positional array makes declaration order load
54
+ * bearing. `u_origin` and `u_scale` are supplied by the renderer.
55
+ */
56
+ export const PLANE_SOURCE = `
57
+ uniform vec4 u_paper;
58
+ uniform vec4 u_minorColor;
59
+ uniform vec4 u_majorColor;
60
+ uniform vec4 u_axisColor;
61
+ uniform vec2 u_origin;
62
+ uniform vec2 u_pitch;
63
+ uniform vec2 u_phase;
64
+ uniform vec2 u_divisions;
65
+ uniform vec2 u_axisAt;
66
+ uniform float u_majorHalf;
67
+ uniform float u_minorHalf;
68
+ uniform float u_axisHalf;
69
+ uniform float u_scale;
70
+
71
+ // Coverage of a line \`d\` px away, of half-width \`w\`, ramped over \`feather\`.
72
+ //
73
+ // The zero test is not a shortcut. The ramp is centred on the line, so a line of
74
+ // no width still lands half a pixel of coverage on the pixels it runs through —
75
+ // an order you switched off, drawn faintly. Answering it here rather than in the
76
+ // ramp is what lets one compiled program serve showGrid, showMinorGrid, showAxes
77
+ // and a grid width of zero.
78
+ float ink(float d, float w, float feather) {
79
+ if (w <= 0.0) return 0.0;
80
+ return clamp((w - d) / feather + 0.5, 0.0, 1.0);
81
+ }
82
+
83
+ // Distance from \`t\` to the nearest ruling. SkSL's mod is floor-based, so this
84
+ // is right on either side of the phase without a sign test.
85
+ float toRuling(float t, float pitch, float phase) {
86
+ float m = mod(t - phase, pitch);
87
+ return min(m, pitch - m);
88
+ }
89
+
90
+ // A straight colour under a coverage, premultiplied.
91
+ vec4 lay(vec4 color, float cover) {
92
+ float a = color.a * cover;
93
+ return vec4(color.rgb * a, a);
94
+ }
95
+
96
+ // \`src\` over \`dst\`, both premultiplied.
97
+ vec4 stack(vec4 src, vec4 dst) {
98
+ return src + dst * (1.0 - src.a);
99
+ }
100
+
101
+ vec4 main(vec2 fragCoord) {
102
+ vec2 p = fragCoord - u_origin;
103
+ float feather = 1.0 / max(u_scale, 0.0001);
104
+
105
+ // Floored well above zero: a mod by zero paints garbage rather than nothing.
106
+ vec2 major = max(u_pitch, vec2(0.01));
107
+ vec2 minor = major / max(u_divisions, vec2(1.0));
108
+
109
+ vec2 dMajor = vec2(toRuling(p.x, major.x, u_phase.x),
110
+ toRuling(p.y, major.y, u_phase.y));
111
+ vec2 dMinor = vec2(toRuling(p.x, minor.x, u_phase.x),
112
+ toRuling(p.y, minor.y, u_phase.y));
113
+
114
+ float majorCover = max(ink(dMajor.x, u_majorHalf, feather),
115
+ ink(dMajor.y, u_majorHalf, feather));
116
+
117
+ // Every numbered ruling is also a faint one — the minor pitch divides the
118
+ // major exactly — so the faint order is cut away inside the numbered one's
119
+ // whole footprint, its ramp included. Drawing both would leave a hairline of
120
+ // the faint colour either side of every numbered line, where the numbered
121
+ // one's own ramp has not yet reached full coverage. The old ladder dropped
122
+ // coincident minors from a list; a shader has no list, so it subtracts.
123
+ float minorCover = max(
124
+ ink(dMinor.x, u_minorHalf, feather)
125
+ * (1.0 - ink(dMajor.x, u_majorHalf + feather, feather)),
126
+ ink(dMinor.y, u_minorHalf, feather)
127
+ * (1.0 - ink(dMajor.y, u_majorHalf + feather, feather)));
128
+
129
+ float axisCover = max(ink(abs(p.x - u_axisAt.x), u_axisHalf, feather),
130
+ ink(abs(p.y - u_axisAt.y), u_axisHalf, feather));
131
+
132
+ vec4 c = lay(u_paper, 1.0);
133
+ c = stack(lay(u_minorColor, minorCover), c);
134
+ c = stack(lay(u_majorColor, majorCover), c);
135
+ c = stack(lay(u_axisColor, axisCover), c);
136
+ return c;
137
+ }
138
+ `;
139
+ /**
140
+ * Where an axis is parked when it is not on screen, in box pixels.
141
+ *
142
+ * Finite on purpose: an infinity here would reach the coverage ramp as a `NaN`,
143
+ * and a `NaN` compares false against every clamp, which paints the axis colour
144
+ * over the entire plane rather than nowhere on it.
145
+ */
146
+ const AXIS_OFF = -1e7;
147
+ /** Two thirds the width of a numbered ruling — the faint order's weight. */
148
+ const MINOR_WEIGHT = 2 / 3;
149
+ /** Past this an integer index is no longer exact, so the reduction is meaningless. */
150
+ const MAX_INDEX = Number.MAX_SAFE_INTEGER;
151
+ /**
152
+ * Whether the plane has anything to paint at all — no paper, no rulings and no
153
+ * axes is a draw the node can skip outright.
154
+ */
155
+ export function planeVisible(style) {
156
+ if (style.paper[3] * clamp01(style.paperOpacity) > 0.001)
157
+ return true;
158
+ if (style.showGrid && style.lineWidth > 0)
159
+ return true;
160
+ return style.showAxes && style.axisWidth > 0;
161
+ }
162
+ /**
163
+ * The uniforms that put {@link PLANE_SOURCE} over a resolved plane.
164
+ *
165
+ * Everything handed over is bounded by the node's box — a pitch of tens of
166
+ * pixels, a phase inside one cell, an axis position inside the frame or parked
167
+ * at {@link AXIS_OFF}. That is deliberate and it is what makes the deep zoom
168
+ * work: see the module note.
169
+ */
170
+ export function planeUniforms(plane, style, xTicks, yTicks) {
171
+ const ruled = style.showGrid && style.lineWidth > 0;
172
+ const majorHalf = ruled ? style.lineWidth / 2 : 0;
173
+ return {
174
+ u_paper: withAlpha(style.paper, clamp01(style.paperOpacity)),
175
+ u_minorColor: rgba(style.colorMinorGrid),
176
+ u_majorColor: rgba(style.colorGrid),
177
+ u_axisColor: rgba(style.colorAxis),
178
+ u_pitch: [
179
+ pitchOf(xTicks.step, plane.scaleX),
180
+ pitchOf(yTicks.step, plane.scaleY),
181
+ ],
182
+ // The shader's y runs down the box and the graph's runs up it, which is the
183
+ // whole of the difference between these two: one signed scale each.
184
+ u_phase: [
185
+ phaseOf(plane.centerX, xTicks.step, plane.width / 2, -plane.scaleX),
186
+ phaseOf(plane.centerY, yTicks.step, plane.height / 2, plane.scaleY),
187
+ ],
188
+ u_divisions: [
189
+ Math.max(1, Math.round(xTicks.divisions)),
190
+ Math.max(1, Math.round(yTicks.divisions)),
191
+ ],
192
+ u_axisAt: [
193
+ // Only ever evaluated when the origin is inside the range, which bounds
194
+ // `centre × scale` by half the box — the same reduction `phaseOf` makes,
195
+ // for free.
196
+ style.showAxes && plane.xMin <= 0 && 0 <= plane.xMax
197
+ ? plane.width / 2 - plane.centerX * plane.scaleX
198
+ : AXIS_OFF,
199
+ style.showAxes && plane.yMin <= 0 && 0 <= plane.yMax
200
+ ? plane.height / 2 + plane.centerY * plane.scaleY
201
+ : AXIS_OFF,
202
+ ],
203
+ u_majorHalf: majorHalf,
204
+ u_minorHalf: ruled && style.showMinorGrid ? majorHalf * MINOR_WEIGHT : 0,
205
+ u_axisHalf: style.showAxes ? Math.max(0, style.axisWidth) / 2 : 0,
206
+ };
207
+ }
208
+ /** A ladder step in pixels, floored where the shader floors it. */
209
+ function pitchOf(step, scale) {
210
+ const pitch = Math.abs(step * scale);
211
+ return Number.isFinite(pitch) && pitch > 0.01 ? pitch : 0.01;
212
+ }
213
+ /**
214
+ * Where the first ruling sits inside the box, in pixels from its leading edge —
215
+ * a number in `[0, pitch)`.
216
+ *
217
+ * The centre is split into a whole number of steps plus a remainder *before* it
218
+ * is ever multiplied by the scale, so the only quantity that ever carries the
219
+ * centre's own magnitude is an integer count that then cancels. Subtracting a
220
+ * tick value from the centre directly is exact at a span of 20 and has no
221
+ * significant digits left at 1e-9 around x = 1e6 — where the lines would still
222
+ * be drawn, just not evenly spaced.
223
+ *
224
+ * `signedScale` is negative for x and positive for y; see {@link planeUniforms}.
225
+ */
226
+ function phaseOf(center, step, halfBox, signedScale) {
227
+ const pitch = pitchOf(step, signedScale);
228
+ const index = Math.round(center / step);
229
+ if (!Number.isFinite(index) || Math.abs(index) > MAX_INDEX)
230
+ return 0;
231
+ const remainder = center - index * step;
232
+ const anchor = halfBox + remainder * signedScale;
233
+ if (!Number.isFinite(anchor))
234
+ return 0;
235
+ return anchor - Math.floor(anchor / pitch) * pitch;
236
+ }
237
+ function rgba(color) {
238
+ return [color[0], color[1], color[2], color[3]];
239
+ }
240
+ function withAlpha(color, opacity) {
241
+ return [color[0], color[1], color[2], color[3] * opacity];
242
+ }
243
+ function clamp01(value) {
244
+ if (!Number.isFinite(value))
245
+ return 1;
246
+ return value < 0 ? 0 : value > 1 ? 1 : value;
247
+ }
248
+ //# sourceMappingURL=plane-fill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plane-fill.js","sourceRoot":"","sources":["../../src/graph2d/plane-fill.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The ruled plane as a **paint** rather than as geometry.\n *\n * The grid used to be one filled rect per line — up to four hundred per axis per\n * order, chained into one `Graphics` so at least the *draws* stayed constant.\n * That is the arrangement `ChartCanvas` makes, and for a chart it is right: a\n * chart's axes are ruled once and then sit still. A grapher's are not. Every\n * rect moves on every frame of a pan, so the renderer's shape cache never hits;\n * worse, the rect *count* changes as the ladder steps, and the cache is keyed by\n * shape index, so a grid that gained a line shifted the key of every curve drawn\n * after it and threw those away too.\n *\n * A shader has none of that. The plane is one shape whatever the zoom, so the\n * curves' cache keys are stable, the wasm path behind it is built once, and the\n * cost of ruling the plane stops depending on how much of it is ruled — which is\n * the whole reason the old {@link MAX_TICKS} cap existed.\n *\n * ## Why not the built-in `grid` fill\n *\n * `Fills.grid` measures its pitch in *divisions of the shape* — deliberately, so\n * a grid of eight stays a grid of eight when the shape resizes. A graph's pitch\n * is decided by the tick ladder and is a different number of pixels on each\n * axis, which one scalar `divisions` cannot say; and it carries one colour,\n * where a graph has two orders in two colours plus its axes.\n *\n * ## Everything variable is a uniform\n *\n * `getOrCompileSkSL` caches by *source string* and never evicts, so a source\n * built per frame is a new program per frame. {@link PLANE_SOURCE} is therefore a\n * module constant and every switch — the two orders, the axes, the paper — is\n * expressed by a uniform that can go to zero.\n *\n * ## The phase is the whole trick\n *\n * Uniforms reach the shader as **fp32**. Handing it \"where x = 0 is\" would be\n * fine at the default zoom and 1e15 pixels at a deep one off the origin, which\n * is not a number fp32 holds at all. So {@link planeUniforms} does the reduction\n * in double precision on the way in: it splits the centre into a whole number of\n * steps plus a remainder *before* anything is multiplied by the scale, and hands\n * over a phase inside a single cell. Every number the shader sees is bounded by\n * the node's own box.\n */\n\nimport type { NormalizedColor } from \"@motionscript/sdk\"\n\nimport type { ResolvedPlane, TickLadder } from \"./plane\"\n\n/**\n * The plane's paint program.\n *\n * Coverage-based rather than a hard test, and the ramp is one *device* pixel\n * wide (`1 / u_scale`, the idiom every built-in pattern fill uses), so a ruling\n * is a crisp hairline whether the camera is zoomed in on the node or the scene\n * is being exported at four times scale.\n *\n * Uniforms are declared widest-first out of habit rather than necessity — a\n * shader fill's uniforms are bound by *name* through reflection, unlike the\n * built-in fills, whose flat positional array makes declaration order load\n * bearing. `u_origin` and `u_scale` are supplied by the renderer.\n */\nexport const PLANE_SOURCE = `\nuniform vec4 u_paper;\nuniform vec4 u_minorColor;\nuniform vec4 u_majorColor;\nuniform vec4 u_axisColor;\nuniform vec2 u_origin;\nuniform vec2 u_pitch;\nuniform vec2 u_phase;\nuniform vec2 u_divisions;\nuniform vec2 u_axisAt;\nuniform float u_majorHalf;\nuniform float u_minorHalf;\nuniform float u_axisHalf;\nuniform float u_scale;\n\n// Coverage of a line \\`d\\` px away, of half-width \\`w\\`, ramped over \\`feather\\`.\n//\n// The zero test is not a shortcut. The ramp is centred on the line, so a line of\n// no width still lands half a pixel of coverage on the pixels it runs through —\n// an order you switched off, drawn faintly. Answering it here rather than in the\n// ramp is what lets one compiled program serve showGrid, showMinorGrid, showAxes\n// and a grid width of zero.\nfloat ink(float d, float w, float feather) {\n if (w <= 0.0) return 0.0;\n return clamp((w - d) / feather + 0.5, 0.0, 1.0);\n}\n\n// Distance from \\`t\\` to the nearest ruling. SkSL's mod is floor-based, so this\n// is right on either side of the phase without a sign test.\nfloat toRuling(float t, float pitch, float phase) {\n float m = mod(t - phase, pitch);\n return min(m, pitch - m);\n}\n\n// A straight colour under a coverage, premultiplied.\nvec4 lay(vec4 color, float cover) {\n float a = color.a * cover;\n return vec4(color.rgb * a, a);\n}\n\n// \\`src\\` over \\`dst\\`, both premultiplied.\nvec4 stack(vec4 src, vec4 dst) {\n return src + dst * (1.0 - src.a);\n}\n\nvec4 main(vec2 fragCoord) {\n vec2 p = fragCoord - u_origin;\n float feather = 1.0 / max(u_scale, 0.0001);\n\n // Floored well above zero: a mod by zero paints garbage rather than nothing.\n vec2 major = max(u_pitch, vec2(0.01));\n vec2 minor = major / max(u_divisions, vec2(1.0));\n\n vec2 dMajor = vec2(toRuling(p.x, major.x, u_phase.x),\n toRuling(p.y, major.y, u_phase.y));\n vec2 dMinor = vec2(toRuling(p.x, minor.x, u_phase.x),\n toRuling(p.y, minor.y, u_phase.y));\n\n float majorCover = max(ink(dMajor.x, u_majorHalf, feather),\n ink(dMajor.y, u_majorHalf, feather));\n\n // Every numbered ruling is also a faint one — the minor pitch divides the\n // major exactly — so the faint order is cut away inside the numbered one's\n // whole footprint, its ramp included. Drawing both would leave a hairline of\n // the faint colour either side of every numbered line, where the numbered\n // one's own ramp has not yet reached full coverage. The old ladder dropped\n // coincident minors from a list; a shader has no list, so it subtracts.\n float minorCover = max(\n ink(dMinor.x, u_minorHalf, feather)\n * (1.0 - ink(dMajor.x, u_majorHalf + feather, feather)),\n ink(dMinor.y, u_minorHalf, feather)\n * (1.0 - ink(dMajor.y, u_majorHalf + feather, feather)));\n\n float axisCover = max(ink(abs(p.x - u_axisAt.x), u_axisHalf, feather),\n ink(abs(p.y - u_axisAt.y), u_axisHalf, feather));\n\n vec4 c = lay(u_paper, 1.0);\n c = stack(lay(u_minorColor, minorCover), c);\n c = stack(lay(u_majorColor, majorCover), c);\n c = stack(lay(u_axisColor, axisCover), c);\n return c;\n}\n`\n\n/** What {@link planeUniforms} needs to know about how the plane is styled. */\nexport interface PlaneStyle {\n /** The paper's colour. */\n paper: NormalizedColor\n /** How opaque the paper is, 0–1. At 0 the graph draws over what is behind it. */\n paperOpacity: number\n showGrid: boolean\n showMinorGrid: boolean\n showAxes: boolean\n colorGrid: NormalizedColor\n colorMinorGrid: NormalizedColor\n colorAxis: NormalizedColor\n /** Width of a numbered ruling. The faint ones are drawn at two thirds of it. */\n lineWidth: number\n /** Width of the two lines through the origin. */\n axisWidth: number\n}\n\n/**\n * The uniform record {@link PLANE_SOURCE} is fed.\n *\n * A type alias rather than an interface so it satisfies the renderer's\n * `Record<string, number | number[]>` without a cast — interfaces have no\n * implicit index signature.\n */\nexport type PlaneUniforms = {\n u_paper: number[]\n u_minorColor: number[]\n u_majorColor: number[]\n u_axisColor: number[]\n u_pitch: number[]\n u_phase: number[]\n u_divisions: number[]\n u_axisAt: number[]\n u_majorHalf: number\n u_minorHalf: number\n u_axisHalf: number\n}\n\n/**\n * Where an axis is parked when it is not on screen, in box pixels.\n *\n * Finite on purpose: an infinity here would reach the coverage ramp as a `NaN`,\n * and a `NaN` compares false against every clamp, which paints the axis colour\n * over the entire plane rather than nowhere on it.\n */\nconst AXIS_OFF = -1e7\n\n/** Two thirds the width of a numbered ruling — the faint order's weight. */\nconst MINOR_WEIGHT = 2 / 3\n\n/** Past this an integer index is no longer exact, so the reduction is meaningless. */\nconst MAX_INDEX = Number.MAX_SAFE_INTEGER\n\n/**\n * Whether the plane has anything to paint at all — no paper, no rulings and no\n * axes is a draw the node can skip outright.\n */\nexport function planeVisible(style: PlaneStyle): boolean {\n if (style.paper[3] * clamp01(style.paperOpacity) > 0.001) return true\n if (style.showGrid && style.lineWidth > 0) return true\n return style.showAxes && style.axisWidth > 0\n}\n\n/**\n * The uniforms that put {@link PLANE_SOURCE} over a resolved plane.\n *\n * Everything handed over is bounded by the node's box — a pitch of tens of\n * pixels, a phase inside one cell, an axis position inside the frame or parked\n * at {@link AXIS_OFF}. That is deliberate and it is what makes the deep zoom\n * work: see the module note.\n */\nexport function planeUniforms(\n plane: ResolvedPlane,\n style: PlaneStyle,\n xTicks: TickLadder,\n yTicks: TickLadder\n): PlaneUniforms {\n const ruled = style.showGrid && style.lineWidth > 0\n const majorHalf = ruled ? style.lineWidth / 2 : 0\n\n return {\n u_paper: withAlpha(style.paper, clamp01(style.paperOpacity)),\n u_minorColor: rgba(style.colorMinorGrid),\n u_majorColor: rgba(style.colorGrid),\n u_axisColor: rgba(style.colorAxis),\n u_pitch: [\n pitchOf(xTicks.step, plane.scaleX),\n pitchOf(yTicks.step, plane.scaleY),\n ],\n // The shader's y runs down the box and the graph's runs up it, which is the\n // whole of the difference between these two: one signed scale each.\n u_phase: [\n phaseOf(plane.centerX, xTicks.step, plane.width / 2, -plane.scaleX),\n phaseOf(plane.centerY, yTicks.step, plane.height / 2, plane.scaleY),\n ],\n u_divisions: [\n Math.max(1, Math.round(xTicks.divisions)),\n Math.max(1, Math.round(yTicks.divisions)),\n ],\n u_axisAt: [\n // Only ever evaluated when the origin is inside the range, which bounds\n // `centre × scale` by half the box — the same reduction `phaseOf` makes,\n // for free.\n style.showAxes && plane.xMin <= 0 && 0 <= plane.xMax\n ? plane.width / 2 - plane.centerX * plane.scaleX\n : AXIS_OFF,\n style.showAxes && plane.yMin <= 0 && 0 <= plane.yMax\n ? plane.height / 2 + plane.centerY * plane.scaleY\n : AXIS_OFF,\n ],\n u_majorHalf: majorHalf,\n u_minorHalf: ruled && style.showMinorGrid ? majorHalf * MINOR_WEIGHT : 0,\n u_axisHalf: style.showAxes ? Math.max(0, style.axisWidth) / 2 : 0,\n }\n}\n\n/** A ladder step in pixels, floored where the shader floors it. */\nfunction pitchOf(step: number, scale: number): number {\n const pitch = Math.abs(step * scale)\n return Number.isFinite(pitch) && pitch > 0.01 ? pitch : 0.01\n}\n\n/**\n * Where the first ruling sits inside the box, in pixels from its leading edge —\n * a number in `[0, pitch)`.\n *\n * The centre is split into a whole number of steps plus a remainder *before* it\n * is ever multiplied by the scale, so the only quantity that ever carries the\n * centre's own magnitude is an integer count that then cancels. Subtracting a\n * tick value from the centre directly is exact at a span of 20 and has no\n * significant digits left at 1e-9 around x = 1e6 — where the lines would still\n * be drawn, just not evenly spaced.\n *\n * `signedScale` is negative for x and positive for y; see {@link planeUniforms}.\n */\nfunction phaseOf(\n center: number,\n step: number,\n halfBox: number,\n signedScale: number\n): number {\n const pitch = pitchOf(step, signedScale)\n const index = Math.round(center / step)\n if (!Number.isFinite(index) || Math.abs(index) > MAX_INDEX) return 0\n\n const remainder = center - index * step\n const anchor = halfBox + remainder * signedScale\n if (!Number.isFinite(anchor)) return 0\n return anchor - Math.floor(anchor / pitch) * pitch\n}\n\nfunction rgba(color: NormalizedColor): number[] {\n return [color[0], color[1], color[2], color[3]]\n}\n\nfunction withAlpha(color: NormalizedColor, opacity: number): number[] {\n return [color[0], color[1], color[2], color[3] * opacity]\n}\n\nfunction clamp01(value: number): number {\n if (!Number.isFinite(value)) return 1\n return value < 0 ? 0 : value > 1 ? 1 : value\n}\n"]}