@motionscript/plot 0.0.0-stage → 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/LICENSE +201 -0
- package/dist/browser/chunks/chunk-RCFWYW6O.js +2 -0
- package/dist/browser/chunks/chunk-RCFWYW6O.js.map +7 -0
- package/dist/browser/index.js +84 -0
- package/dist/browser/index.js.map +7 -0
- package/dist/browser/kit.js +2 -0
- package/dist/browser/kit.js.map +7 -0
- package/dist/browser/manifest.json +12 -0
- package/dist/engine.d.ts +14 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +14 -0
- package/dist/engine.js.map +1 -0
- package/dist/graph2d/curve-cache.d.ts +60 -0
- package/dist/graph2d/curve-cache.d.ts.map +1 -0
- package/dist/graph2d/curve-cache.js +79 -0
- package/dist/graph2d/curve-cache.js.map +1 -0
- package/dist/graph2d/curve-error.d.ts +12 -0
- package/dist/graph2d/curve-error.d.ts.map +1 -0
- package/dist/graph2d/curve-error.js +15 -0
- package/dist/graph2d/curve-error.js.map +1 -0
- package/dist/graph2d/curve.d.ts +104 -0
- package/dist/graph2d/curve.d.ts.map +1 -0
- package/dist/graph2d/curve.js +531 -0
- package/dist/graph2d/curve.js.map +1 -0
- package/dist/graph2d/graph2d.d.ts +164 -0
- package/dist/graph2d/graph2d.d.ts.map +1 -0
- package/dist/graph2d/graph2d.js +404 -0
- package/dist/graph2d/graph2d.js.map +1 -0
- package/dist/graph2d/index.d.ts +32 -0
- package/dist/graph2d/index.d.ts.map +1 -0
- package/dist/graph2d/index.js +32 -0
- package/dist/graph2d/index.js.map +1 -0
- package/dist/graph2d/plane-fill.d.ts +110 -0
- package/dist/graph2d/plane-fill.d.ts.map +1 -0
- package/dist/graph2d/plane-fill.js +248 -0
- package/dist/graph2d/plane-fill.js.map +1 -0
- package/dist/graph2d/plane.d.ts +179 -0
- package/dist/graph2d/plane.d.ts.map +1 -0
- package/dist/graph2d/plane.js +359 -0
- package/dist/graph2d/plane.js.map +1 -0
- package/dist/graph2d/shared.d.ts +40 -0
- package/dist/graph2d/shared.d.ts.map +1 -0
- package/dist/graph2d/shared.js +70 -0
- package/dist/graph2d/shared.js.map +1 -0
- package/dist/graph3d/expression.d.ts +30 -0
- package/dist/graph3d/expression.d.ts.map +1 -0
- package/dist/graph3d/expression.js +35 -0
- package/dist/graph3d/expression.js.map +1 -0
- package/dist/graph3d/graph3d.d.ts +186 -0
- package/dist/graph3d/graph3d.d.ts.map +1 -0
- package/dist/graph3d/graph3d.js +404 -0
- package/dist/graph3d/graph3d.js.map +1 -0
- package/dist/graph3d/index.d.ts +22 -0
- package/dist/graph3d/index.d.ts.map +1 -0
- package/dist/graph3d/index.js +22 -0
- package/dist/graph3d/index.js.map +1 -0
- package/dist/graph3d/shared.d.ts +61 -0
- package/dist/graph3d/shared.d.ts.map +1 -0
- package/dist/graph3d/shared.js +101 -0
- package/dist/graph3d/shared.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/kit/equations.d.ts +56 -0
- package/dist/kit/equations.d.ts.map +1 -0
- package/dist/kit/equations.js +63 -0
- package/dist/kit/equations.js.map +1 -0
- package/dist/kit/expression.d.ts +60 -0
- package/dist/kit/expression.d.ts.map +1 -0
- package/dist/kit/expression.js +268 -0
- package/dist/kit/expression.js.map +1 -0
- package/dist/kit/index.d.ts +15 -0
- package/dist/kit/index.d.ts.map +1 -0
- package/dist/kit/index.js +13 -0
- package/dist/kit/index.js.map +1 -0
- package/dist/nodes.d.ts +19 -0
- package/dist/nodes.d.ts.map +1 -0
- package/dist/nodes.js +19 -0
- package/dist/nodes.js.map +1 -0
- package/package.json +68 -3
- package/registry.json +34 -0
- package/src/engine.ts +13 -0
- package/src/graph2d/curve-cache.ts +103 -0
- package/src/graph2d/curve-error.ts +19 -0
- package/src/graph2d/curve.ts +657 -0
- package/src/graph2d/graph2d.ts +586 -0
- package/src/graph2d/index.ts +31 -0
- package/src/graph2d/plane-fill.ts +308 -0
- package/src/graph2d/plane.ts +457 -0
- package/src/graph2d/shared.ts +103 -0
- package/src/graph3d/expression.ts +51 -0
- package/src/graph3d/graph3d.ts +614 -0
- package/src/graph3d/index.ts +21 -0
- package/src/graph3d/shared.ts +143 -0
- package/src/index.ts +3 -0
- package/src/kit/equations.ts +102 -0
- package/src/kit/expression.ts +323 -0
- package/src/kit/index.ts +29 -0
- package/src/nodes.ts +19 -0
- package/README.md +0 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"curve.js","sourceRoot":"","sources":["../../src/graph2d/curve.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Turning `y = f(x)` into polylines you can actually hand a rasterizer.\n *\n * This is the whole difficulty of a 2D grapher, and it is not the sampling loop\n * anybody writes first. That loop — walk the pixel columns, evaluate, skip\n * anything that isn't on screen — fails in four separate ways, and each failure\n * looks like a *different* bug:\n *\n * 1. **Dropping off-view points instead of clipping segments.** If the last\n * point you keep is the last one that landed inside the box, the curve stops\n * in mid-air an arbitrary distance from the edge. `log(x)` appears to end\n * somewhere over the plot rather than plunging out of the bottom of it. The\n * fix is to build the segment anyway and clip *it* against the frame, so the\n * curve leaves through the edge exactly where it really crosses.\n *\n * 2. **Handing the path API astronomical coordinates.** `log(1e-300)` is about\n * -690, which at any sane zoom is millions of pixels below the box. Skia's\n * scan converter is fixed-point, so a coordinate past roughly ±32k overflows\n * and the contour is *silently dropped* — the curve doesn't render wrong, it\n * doesn't render at all. Clipping (1) already bounds what reaches the path\n * API to the frame plus a hair; {@link COORD_LIMIT} bounds the arithmetic on\n * the way there, so no infinity ever reaches the clipper to turn a `t` into a\n * `NaN`.\n *\n * 3. **Sampling uniformly in x.** Near an asymptote one pixel of x is thousands\n * of pixels of y, so a uniform grid gives a visible polygonal kink where the\n * curve turns; far from one, the same grid spends hundreds of samples on a\n * straight line. {@link sampleCurve} subdivides recursively instead — split\n * any segment that is not flat *in screen space*, stop at a quarter pixel —\n * so sample density follows curvature and comes out dense near `x = 0` for\n * `log` and sparse where it is nearly straight. The flatness test pairs a\n * distance check with an **angle** check, because the distance alone reads a\n * symmetric wiggle (whose midpoint sits exactly on the chord) as flat.\n *\n * 4. **Treating \"undefined\" and \"very large\" as the same thing.** `log(-1)` is\n * `NaN`: a genuine hole, and the path has to break. `log(1e-300)` is finite\n * and enormous: the curve is still there, still continuous, and has to keep\n * being drawn until it leaves the frame. Conflating the two is the other half\n * of the cut-off in (1). At a defined/undefined edge this bisects to find\n * where the domain actually ends, so `sqrt(x)` terminates at the origin\n * rather than at whichever sample happened to be last.\n *\n * Everything here is pure and works in node-local pixels, so it is testable with\n * no renderer: the caller supplies the graph→pixel mapping and gets back runs of\n * points ready to stroke.\n */\n\nimport type { Vector2 } from \"@motionscript/core\"\n\n/**\n * The graph→pixel mapping, as four numbers rather than two closures: this is the\n * hot path (a few thousand projections per curve per frame) and the closures\n * would be called through a megamorphic call site.\n *\n * Pixels are the node's own local space — origin at the node's centre, y **up**,\n * which is both the space `Graphics` draws in and, conveniently, the orientation\n * a graph is thought about in.\n */\nexport interface PlaneMap {\n /** Graph x at the node's horizontal centre. */\n centerX: number\n /** Graph y at the node's vertical centre. */\n centerY: number\n /** Pixels per graph unit along x. */\n scaleX: number\n /** Pixels per graph unit along y. */\n scaleY: number\n}\n\n/** An axis-aligned rectangle in node-local pixels, y-up. */\nexport interface PixelRect {\n left: number\n right: number\n bottom: number\n top: number\n}\n\n/**\n * Hard bound on a projected coordinate, in node pixels.\n *\n * Not a rendering decision — clipping is what decides what the rasterizer sees.\n * This exists so the *arithmetic* between projection and clipping stays finite:\n * `1/x` at 1e-320 projects to something that overflows to `Infinity`, and an\n * `Infinity` in the clipper's `q / p` produces a `NaN` `t` that quietly discards\n * the segment — which would put the cut-off of (1) back in through a side door.\n *\n * Large enough that no clamped point is ever inside a plausible frame (so the\n * clamp can't bend a visible part of the curve), small enough to stay far below\n * the fixed-point limit even after a parent scales the node up.\n */\nconst COORD_LIMIT = 16_000\n\n/** How far a sample may sit from the chord before the segment is split, in px. */\nconst TOLERANCE_PX = 0.25\n\n/**\n * How narrow a segment may get before the subdivision stops, in pixels of x.\n *\n * A **pixel** size rather than a recursion depth, and that is the single change\n * that bounds this module's cost. Leaves tile the frame, so however violent the\n * function is the whole tree costs at most `2 · width / MIN_SEGMENT_PX`\n * evaluations — about seven and a half thousand on a 1920-px node, against the\n * million a depth of ten over five hundred seeds allowed. Half a pixel because\n * that is where one more halving cannot move a stroked line by as much as its\n * own antialiasing.\n */\nconst MIN_SEGMENT_PX = 0.5\n\n/**\n * How far the two halves of a segment may disagree in direction before it is\n * split, in radians (~2.9°).\n *\n * The reason the distance test alone is not enough: a symmetric wiggle puts its\n * midpoint *exactly* on the chord, so the perpendicular distance is zero and a\n * distance-only test declares a visible kink flat. Two tests, and a segment has\n * to pass both.\n */\nconst ANGLE_TOLERANCE = 0.05\n\n/**\n * Backstop on the recursion depth.\n *\n * {@link MIN_SEGMENT_PX} stops the subdivision first at every box size\n * {@link seedCount} can produce — the deepest is about six levels on an 8000-px\n * node — so this only ever fires if a seed interval somehow came out enormous.\n * It is here so the recursion cannot run away on a stack rather than because any\n * curve needs it.\n */\nconst MAX_DEPTH = 12\n\n/**\n * Evaluations one curve may spend in one frame.\n *\n * The refinement alone cannot reach this — see {@link MIN_SEGMENT_PX} — so what\n * this really bounds is the boundary bisection, which is logarithmic per domain\n * edge but unbounded in the *number* of edges: `sqrt(sin(1000x))` has hundreds.\n * At roughly a tenth of a microsecond for a compiled expression tree this is\n * about four milliseconds, which is as much of a 60 fps frame as one curve may\n * have to itself.\n */\nconst MAX_EVALUATIONS = 40_000\n\n/**\n * Evaluations every curve is guaranteed, however many of them there are: the\n * seed pass with room for two levels of refinement over all of it.\n *\n * A graph carrying twenty curves draws all twenty coarsely rather than the first\n * three well and the rest not at all.\n */\nconst MIN_EVALUATIONS = 2_048\n\n/**\n * Evaluations all the curves on one node share in a frame.\n *\n * Three at their individual ceiling, or the verification harness's six at twenty\n * thousand each — about twelve milliseconds in the worst case, which is the\n * point past which degrading the curves beats dropping the frame.\n */\nconst FRAME_EVALUATIONS = 120_000\n\n/**\n * How many times a defined/undefined edge is bisected before the curve is cut.\n *\n * Generous because the interesting case is not `sqrt(x)`, where the value at the\n * boundary is finite and a handful of steps land on it. It is `log(x)`, whose\n * boundary value is unbounded: each halving buys a constant amount of extra\n * descent, so terminating early leaves the curve stopping *inside* the frame,\n * which is the exact artefact this module exists to prevent. The loop exits as\n * soon as the defined side has left the frame, so the full count is only ever\n * paid on a boundary that stays on screen.\n */\nconst BOUNDARY_STEPS = 60\n\n/** Roughly how far apart the initial uniform samples are laid, in pixels. */\nconst SEED_SPACING_PX = 8\nconst MIN_SEEDS = 64\nconst MAX_SEEDS = 512\n\n/**\n * Ceiling on the points one curve may emit.\n *\n * Adaptive subdivision is self-limiting on anything anybody plots deliberately;\n * it is not self-limiting on `sin(10000x)`, where every segment genuinely is\n * curved at every scale and the recursion expands to its full width. This is the\n * backstop, and going over it degrades the curve to the resolution already\n * reached rather than dropping it.\n */\nconst MAX_POINTS = 24_000\n\n/** A `y = f(x)` curve, sampled by {@link sampleCurve}. */\nexport type CurveFunction = (x: number) => number\n\n/** One evaluation of the curve, projected and classified. */\ninterface Sample {\n /**\n * Offset from the view's centre, in graph units — the parameter of record.\n *\n * The sampler walks `t` and not `x` because `px` is then one multiply,\n * `t · scaleX`, instead of a round trip out to `centerX` and back:\n * `x = centerX + t` followed by `(x - centerX) · scaleX` is a literal\n * add-then-subtract through a number whose ulp can be wider than the whole\n * visible window. At `centerX = 1e9` and a span of 1e-6 that round trip leaves\n * eight distinct pixel positions across the screen, and the curve staircases.\n */\n t: number\n /** `centerX + t` — what `f` actually saw, kept for the bisections' float-resolution guards. */\n x: number\n /** Graph y — kept raw, so the sign is readable either side of a pole. */\n y: number\n /** Node-local pixels, clamped to {@link COORD_LIMIT}. */\n px: number\n py: number\n /**\n * Whether there is a point here at all: `false` for `NaN` (outside the\n * function's domain) **and** for `±Infinity` (a pole landed on exactly). Both\n * break the path; what separates them is where the bisection either side\n * converges to, which the caller never has to know.\n */\n ok: boolean\n}\n\nfunction blankSample(): Sample {\n return { t: 0, x: 0, y: 0, px: 0, py: 0, ok: false }\n}\n\n/**\n * Reused {@link Sample} storage.\n *\n * Every evaluation used to be a fresh object. Six curves at sixty frames a\n * second is millions of short-lived allocations a second, which is a collector\n * running *through* the animation rather than between takes. The recursion is a\n * depth-first walk over one seed interval and nothing outlives that interval, so\n * the arena hands out slots and the seed loop resets the mark — which makes\n * every allocation after the first frame a field write on an object whose hidden\n * class never changes.\n *\n * Module scope, and therefore not re-entrant. Nothing re-enters: one\n * {@link sampleCurve} runs to completion before the next begins, and an arena\n * per call would allocate the thing it exists to avoid.\n */\nconst arena: Sample[] = []\nlet arenaMark = 0\n\nfunction takeSample(): Sample {\n const slot = arena[arenaMark]\n if (slot !== undefined) {\n arenaMark++\n return slot\n }\n const fresh = blankSample()\n arena.push(fresh)\n arenaMark++\n return fresh\n}\n\n/**\n * A ceiling on curve evaluations, shared by every curve drawn in one frame.\n *\n * Shared rather than per curve because what a frame can afford is a property of\n * the frame: one curve with a pole every three pixels must not be allowed to\n * cost what six ordinary ones do, and six ordinary ones must not each be held to\n * a sixth of the budget when five of them are straight lines. A cheap curve\n * hands its slack to whatever comes after it.\n */\nexport interface SampleBudget {\n /** Evaluations left for the curves still to be drawn. */\n remaining: number\n /** How many curves are still to be drawn, this one included. */\n pending: number\n}\n\n/** A frame's budget, to be handed to each of `curves` {@link sampleCurve} calls. */\nexport function sampleBudget(curves: number): SampleBudget {\n return { remaining: FRAME_EVALUATIONS, pending: Math.max(1, curves) }\n}\n\n/**\n * Samples `f` across the x range `clip` covers and returns the polylines to\n * stroke: node-local pixel points, clipped to `clip`, split wherever the curve\n * genuinely stops.\n *\n * `clip` should be the node's box grown by about a stroke width, so a curve\n * running just outside the frame still paints the half of its stroke that falls\n * inside it. Nothing outside that rectangle is returned, which is what keeps the\n * rasterizer's fixed-point limit out of reach — see (2) in the module note.\n */\nexport function sampleCurve(\n f: CurveFunction,\n map: PlaneMap,\n clip: PixelRect,\n budget?: SampleBudget\n): Vector2[][] {\n const pen = new CurvePen(clip)\n\n // Sample across the *clip*, not the box: the extra sliver either side is what\n // lets a curve enter the frame already at full stroke width instead of\n // starting with a cap flush against the edge.\n //\n // In offsets from the centre rather than in x — see {@link Sample.t}.\n const tLo = clip.left / map.scaleX\n const tHi = clip.right / map.scaleX\n if (!(tHi > tLo) || !(map.scaleX > 0) || !(map.scaleY > 0)) return []\n\n const seeds = seedCount(clip.right - clip.left)\n const step = (tHi - tLo) / seeds\n\n // This curve's slice of the frame's evaluations. Bounded above so one curve\n // can't eat the frame, and below so a graph full of them still draws every one.\n const share = budget\n ? Math.min(\n MAX_EVALUATIONS,\n Math.max(\n MIN_EVALUATIONS,\n Math.floor(budget.remaining / Math.max(1, budget.pending))\n )\n )\n : MAX_EVALUATIONS\n let spent = 0\n\n const write = (slot: Sample, t: number): Sample => {\n const x = map.centerX + t\n const y = f(x)\n spent++\n const ok = Number.isFinite(y)\n slot.t = t\n slot.x = x\n slot.y = y\n slot.px = clampCoord(t * map.scaleX)\n // A non-finite y has no position; 0 is a placeholder the `ok` flag keeps\n // anyone from reading.\n slot.py = ok ? clampCoord((y - map.centerY) * map.scaleY) : 0\n slot.ok = ok\n return slot\n }\n\n const at = (t: number): Sample => write(takeSample(), t)\n\n /**\n * The last defined sample on the way from `from` towards `to`, where exactly\n * one of the two is defined.\n *\n * Three exits. Off the frame, because past the edge the only thing further\n * halvings buy is a longer segment that is going to be clipped to the same\n * place anyway — this is the one `log` leaves by, and the reason\n * {@link BOUNDARY_STEPS} is generous. *Converged*, because a boundary whose\n * value is finite stops moving after a handful of steps and `sqrt` was paying\n * for all sixty of them. And float resolution, which nothing reaches.\n */\n const edgeOf = (from: Sample, to: Sample): Sample => {\n let inside = from\n let outsideT = to.t\n for (let i = 0; i < BOUNDARY_STEPS; i++) {\n if (spent >= share) break\n if (offFrame(inside, clip)) break\n\n const midT = (inside.t + outsideT) / 2\n if (midT === inside.t || midT === outsideT) break // float resolution\n const mid = at(midT)\n if (!mid.ok) {\n outsideT = midT\n continue\n }\n const settled =\n Math.abs(mid.px - inside.px) < TOLERANCE_PX &&\n Math.abs(mid.py - inside.py) < TOLERANCE_PX\n inside = mid\n if (settled) break\n }\n return inside\n }\n\n const emit = (\n a: Sample,\n m: Sample | null,\n b: Sample,\n floored: boolean\n ): void => {\n if (a.ok && b.ok) {\n if (floored && diverges(a, m, b, clip)) {\n pen.break()\n return\n }\n pen.draw(a, b)\n return\n }\n if (a.ok) {\n const edge = edgeOf(a, b)\n if (edge.t !== a.t) pen.draw(a, edge)\n pen.break()\n return\n }\n if (b.ok) {\n pen.break()\n const edge = edgeOf(b, a)\n if (edge.t !== b.t) pen.draw(edge, b)\n else pen.start(b)\n return\n }\n pen.break()\n }\n\n const refine = (a: Sample, b: Sample, depth: number): void => {\n // Out of budget, or out of room in the path: draw the chord. Deliberately\n // *not* routed through the pole test — with no midpoint there is nothing to\n // judge by, and a vertical line through an asymptote is an ugly frame where\n // a missing branch is a wrong one.\n if (spent >= share || pen.points >= MAX_POINTS) {\n emit(a, null, b, false)\n return\n }\n const m = at((a.t + b.t) / 2)\n const floored = depth >= MAX_DEPTH || b.px - a.px <= MIN_SEGMENT_PX\n if (floored || flat(a, m, b)) {\n // \"Gave up\" rather than \"settled\" is what identifies a pole, which is why\n // the midpoint travels with the verdict — see {@link diverges}.\n emit(a, m, b, floored)\n return\n }\n refine(a, m, depth + 1)\n refine(m, b, depth + 1)\n }\n\n // The seed endpoints come from their own pair rather than from the arena:\n // `previous` has to survive the reset that recycles the interval it was the\n // right-hand end of.\n const ends: [Sample, Sample] = [blankSample(), blankSample()]\n let previous = write(ends[0], tLo)\n for (let i = 1; i <= seeds; i++) {\n // Recomputed from the ends rather than accumulated, so rounding can't drift\n // the grid off the right-hand edge over five hundred additions.\n const next = write(ends[i & 1], i === seeds ? tHi : tLo + i * step)\n arenaMark = 0\n refine(previous, next, 0)\n previous = next\n }\n arenaMark = 0\n\n if (budget) {\n budget.remaining = Math.max(0, budget.remaining - spent)\n budget.pending = Math.max(0, budget.pending - 1)\n }\n\n return pen.finish()\n}\n\n/** How many uniform samples to lay down before refining anything. */\nfunction seedCount(widthPx: number): number {\n const wanted = Math.round(widthPx / SEED_SPACING_PX)\n return Math.min(MAX_SEEDS, Math.max(MIN_SEEDS, wanted))\n}\n\n/**\n * Whether the arc `a → m → b` is straight enough, in **screen** space, to draw\n * as one segment.\n *\n * Screen space and not graph space, because what \"straight enough\" has to mean\n * is \"the eye can't tell\" — a tolerance in graph units would subdivide a flat\n * line to death when zoomed out and give up mid-curve when zoomed in.\n */\nfunction flat(a: Sample, m: Sample, b: Sample): boolean {\n // A domain edge somewhere inside: always split, so the bisection that finds it\n // runs on as short an interval as possible.\n if (a.ok !== m.ok || m.ok !== b.ok) return false\n // Nothing defined anywhere across it — there is no curve here to be wrong\n // about, so stop.\n if (!a.ok) return true\n\n const dx = b.px - a.px\n const dy = b.py - a.py\n const length = Math.hypot(dx, dy) || 1\n const distance = Math.abs((m.px - a.px) * dy - (m.py - a.py) * dx) / length\n if (distance > TOLERANCE_PX) return false\n\n // The second half of the test — see {@link ANGLE_TOLERANCE}.\n const first = Math.atan2(m.py - a.py, m.px - a.px)\n const second = Math.atan2(b.py - m.py, b.px - m.px)\n let turn = Math.abs(first - second)\n if (turn > Math.PI) turn = 2 * Math.PI - turn\n return turn < ANGLE_TOLERANCE\n}\n\n/**\n * Whether a segment the sampler had to give up on is an **asymptote** — the one\n * case where drawing nothing beats drawing the chord.\n *\n * Two conditions, and the second is the one this module was missing. The ends\n * must have run off *opposite* edges, which is what a vertical line through the\n * frame looks like. And the curve must actually turn back inside the segment: a\n * midpoint outside the range its own ends span is a pole (`tan` climbs to +∞ on\n * one side of π/2 and arrives from -∞ on the other, so the midpoint is always\n * outside), while a midpoint *between* them is an ordinary steep climb.\n *\n * Leaving the second test out is what cut `log(x)` off in mid-air. Once the y\n * axis is stretched, `log` crosses the whole visible band inside a single leaf;\n * the ends of that leaf are both off the frame, so its neighbours clip away to\n * nothing, and treating the leaf itself as a pole deletes not a gap but the\n * entire crossing — the exact artefact this module exists to prevent, arriving\n * through the door meant to keep it out. `exp`, `x^20` and `1/x` all reach it.\n *\n * A `null` midpoint means the sampler ran out of budget and has nothing to judge\n * by. Draw it: only a curve that has already spent forty thousand evaluations\n * can get here, and a spurious vertical line is the lesser wrong.\n */\nfunction diverges(\n a: Sample,\n m: Sample | null,\n b: Sample,\n clip: PixelRect\n): boolean {\n if (!straddles(a, b, clip)) return false\n if (m === null) return false\n if (!m.ok) return true\n return m.y < Math.min(a.y, b.y) || m.y > Math.max(a.y, b.y)\n}\n\n/** Whether the two ends have run off *opposite* edges of the frame. */\nfunction straddles(a: Sample, b: Sample, clip: PixelRect): boolean {\n return (\n (a.py > clip.top && b.py < clip.bottom) ||\n (a.py < clip.bottom && b.py > clip.top)\n )\n}\n\n/** Whether a sample has left the frame entirely. */\nfunction offFrame(sample: Sample, clip: PixelRect): boolean {\n return sample.py > clip.top || sample.py < clip.bottom\n}\n\nfunction clampCoord(value: number): number {\n if (value > COORD_LIMIT) return COORD_LIMIT\n if (value < -COORD_LIMIT) return -COORD_LIMIT\n // `NaN` cannot reach here (the caller checks `ok` first), but a `-0` would\n // print oddly in a test snapshot and costs nothing to normalise.\n return value === 0 ? 0 : value\n}\n\n/**\n * Accumulates clipped polylines, one segment at a time.\n *\n * Incremental rather than \"collect every point, then clip the polyline\" because\n * a curve is built by a recursion that already knows where its breaks are, and\n * because the intermediate list is the thing that would carry the astronomical\n * coordinates this module exists to keep out of the renderer.\n */\nclass CurvePen {\n private readonly runs: Vector2[][] = []\n private open: Vector2[] = []\n /** The clipped end of the last drawn segment, for joining the next one to. */\n private cursor: Vector2 | null = null\n /** Points emitted so far, against {@link MAX_POINTS}. */\n points = 0\n\n constructor(private readonly clip: PixelRect) {}\n\n /** Draws `a → b`, clipped; continues the open run when it joins on. */\n draw(a: Sample, b: Sample): void {\n const segment = clipSegment(a.px, a.py, b.px, b.py, this.clip)\n if (!segment) {\n // Wholly outside the frame: whatever run was open ended at the edge.\n this.break()\n return\n }\n const [start, end] = segment\n if (this.cursor && samePoint(this.cursor, start)) {\n this.push(end)\n } else {\n this.break()\n this.push(start)\n this.push(end)\n }\n this.cursor = end\n }\n\n /** Begins a run at `b` without drawing anything into it yet. */\n start(b: Sample): void {\n this.break()\n if (inside(b.px, b.py, this.clip)) {\n this.push({ x: b.px, y: b.py })\n this.cursor = { x: b.px, y: b.py }\n }\n }\n\n /** Ends the open run. A run of fewer than two points draws nothing. */\n break(): void {\n if (this.open.length >= 2) this.runs.push(this.open)\n this.open = []\n this.cursor = null\n }\n\n finish(): Vector2[][] {\n this.break()\n return this.runs\n }\n\n private push(point: Vector2): void {\n this.open.push(point)\n this.points++\n }\n}\n\n/** Points within a tenth of a pixel are the same point, for run-joining. */\nfunction samePoint(a: Vector2, b: Vector2): boolean {\n return Math.abs(a.x - b.x) < 0.1 && Math.abs(a.y - b.y) < 0.1\n}\n\nfunction inside(x: number, y: number, clip: PixelRect): boolean {\n return x >= clip.left && x <= clip.right && y >= clip.bottom && y <= clip.top\n}\n\n/**\n * Liang–Barsky clip of one segment against `clip`. Returns the surviving\n * `[start, end]`, or `null` when the segment lies wholly outside.\n *\n * The piece of this module that turns \"the curve stops in mid-air\" into \"the\n * curve leaves through the edge\": the off-frame endpoint is kept and the\n * *segment* is cut, rather than the point being discarded and the segment never\n * built.\n */\nfunction clipSegment(\n ax: number,\n ay: number,\n bx: number,\n by: number,\n clip: PixelRect\n): [Vector2, Vector2] | null {\n const dx = bx - ax\n const dy = by - ay\n let t0 = 0\n let t1 = 1\n\n // Each edge as (p, q): the segment survives where p·t <= q.\n const edges: [number, number][] = [\n [-dx, ax - clip.left],\n [dx, clip.right - ax],\n [-dy, ay - clip.bottom],\n [dy, clip.top - ay],\n ]\n for (const [p, q] of edges) {\n if (p === 0) {\n // Parallel to this edge: outside it means the whole segment is out.\n if (q < 0) return null\n continue\n }\n const r = q / p\n if (p < 0) {\n if (r > t1) return null\n if (r > t0) t0 = r\n } else {\n if (r < t0) return null\n if (r < t1) t1 = r\n }\n }\n return [\n { x: ax + t0 * dx, y: ay + t0 * dy },\n { x: ax + t1 * dx, y: ay + t1 * dy },\n ]\n}\n"]}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Clip, Node2D, type Color, type Node2DProps, type RenderContext2D } from "@motionscript/core";
|
|
2
|
+
/** One plotted curve, `y = f(x)`. */
|
|
3
|
+
export interface Graph2DEquation {
|
|
4
|
+
/** Identity across list changes — see {@link Graph2D.equations}. */
|
|
5
|
+
id: string | number;
|
|
6
|
+
/** The expression, evaluated as `y = f(x)` (e.g. `sin(x)`, `log(x)`). */
|
|
7
|
+
expression: string;
|
|
8
|
+
/** The curve's colour: any {@link Color} — a CSS string or an RGBA tuple. */
|
|
9
|
+
color: Color;
|
|
10
|
+
/** Whether the curve is drawn. Defaults to true. */
|
|
11
|
+
visible?: boolean;
|
|
12
|
+
/** How opaque the curve is, 0–1. Defaults to 1. */
|
|
13
|
+
opacity?: number;
|
|
14
|
+
}
|
|
15
|
+
/** The paper the curves are drawn on: the grid, the axes and their numbers. */
|
|
16
|
+
export interface Graph2DGrid {
|
|
17
|
+
/** Whether to rule the plane at all. Defaults to true. */
|
|
18
|
+
showGrid?: boolean;
|
|
19
|
+
/** Whether to draw the faint lines between the numbered ones. Defaults to true. */
|
|
20
|
+
showMinorGrid?: boolean;
|
|
21
|
+
/** Whether to draw the two lines through the origin. Defaults to true. */
|
|
22
|
+
showAxes?: boolean;
|
|
23
|
+
/** Whether to number the axes. Defaults to true. */
|
|
24
|
+
showLabels?: boolean;
|
|
25
|
+
colorGrid?: Color;
|
|
26
|
+
colorMinorGrid?: Color;
|
|
27
|
+
colorAxis?: Color;
|
|
28
|
+
colorLabel?: Color;
|
|
29
|
+
/** Width of a grid line. The minor lines are drawn at two thirds of it. */
|
|
30
|
+
lineWidth?: number;
|
|
31
|
+
/** Width of the two axis lines. */
|
|
32
|
+
axisWidth?: number;
|
|
33
|
+
/** Size of the axis numbers. Also sets how close two of them may sit. */
|
|
34
|
+
fontSize?: number;
|
|
35
|
+
/** Family the axis numbers are set in. Empty takes the built-in face. */
|
|
36
|
+
fontFamily?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface Graph2DProps extends Node2DProps {
|
|
39
|
+
/** The curves to plot, each evaluated as `y = f(x)` across the visible range. */
|
|
40
|
+
equations: Graph2DEquation[];
|
|
41
|
+
/** Graph x at the centre of the box — see {@link PlaneView}. */
|
|
42
|
+
centerX: number;
|
|
43
|
+
/** Graph y at the centre of the box. */
|
|
44
|
+
centerY: number;
|
|
45
|
+
/** Width of the visible window, in graph units. */
|
|
46
|
+
xSpan: number;
|
|
47
|
+
/**
|
|
48
|
+
* Height of the visible window in graph units, or **0 to keep units square**,
|
|
49
|
+
* which is the default and almost always what you want. See {@link PlaneView}.
|
|
50
|
+
*/
|
|
51
|
+
ySpan: number;
|
|
52
|
+
grid: Graph2DGrid;
|
|
53
|
+
/** The paper's colour. */
|
|
54
|
+
background: Color;
|
|
55
|
+
/** How opaque the paper is, 0–1. At 0 the graph draws over whatever is behind it. */
|
|
56
|
+
backgroundOpacity: number;
|
|
57
|
+
/** How thick a plotted curve is drawn. */
|
|
58
|
+
lineWidth: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A 2D function grapher — any number of `y = f(x)` curves over a ruled plane,
|
|
62
|
+
* with numbered axes.
|
|
63
|
+
*
|
|
64
|
+
* <Graph2D
|
|
65
|
+
* width={960} height={540}
|
|
66
|
+
* equations={[{ id: 1, expression: "log(x)", color: "#3B82F6" }]}
|
|
67
|
+
* />
|
|
68
|
+
*
|
|
69
|
+
* The sibling of {@link Graph3D}, and deliberately built the same way: the same
|
|
70
|
+
* little expression language, the same equation tiles, the same match-by-id
|
|
71
|
+
* tween over the list. What it does *not* share is the hard part, which is not
|
|
72
|
+
* the maths but the drawing — see `curve.ts`, which exists because the obvious
|
|
73
|
+
* sampling loop cuts `log(x)` off in mid-air, silently drops `1/x` entirely on a
|
|
74
|
+
* fixed-point rasterizer, and puts a visible polygonal kink wherever a curve
|
|
75
|
+
* turns sharply.
|
|
76
|
+
*
|
|
77
|
+
* Every prop is a `@property`, so a constant, a `() => signal()` binding, and
|
|
78
|
+
* `set()`/`to()` on a ref all work and agree. The non-scalar props each declare
|
|
79
|
+
* a `mapper` (loose authored shape → one canonical internal shape) and a `tween`
|
|
80
|
+
* (how that shape interpolates) — without the tween, `to({ grid: … })` would hold
|
|
81
|
+
* the old value and snap at the end, which is the standard trap for an
|
|
82
|
+
* object-valued attribute.
|
|
83
|
+
*
|
|
84
|
+
* **The view is four plain numbers, not a transform.** `centerX`/`centerY` and
|
|
85
|
+
* `xSpan`/`ySpan` are each independently tweenable, which is what makes "pan
|
|
86
|
+
* across while zooming in" one command and what keeps every frame reproducible
|
|
87
|
+
* under scrubbing and export. The canvas's pan/zoom gesture writes exactly these
|
|
88
|
+
* four, so a view flown by hand is a view a `to` can animate afterwards.
|
|
89
|
+
*/
|
|
90
|
+
export declare class Graph2D extends Node2D<Graph2DProps> {
|
|
91
|
+
/**
|
|
92
|
+
* The plotted curves.
|
|
93
|
+
*
|
|
94
|
+
* The mapper compiles each expression **once per write** (memoised by source),
|
|
95
|
+
* so the draw — which re-runs every frame — never re-parses; and it folds
|
|
96
|
+
* `visible`/`opacity` into a single number, so hiding is expressible as a
|
|
97
|
+
* fade. An expression that doesn't parse is dropped and the rest still draw,
|
|
98
|
+
* which is what lets the inspector commit on every keystroke.
|
|
99
|
+
*
|
|
100
|
+
* The tween matches the two lists by `id` — see `lerpCurves`.
|
|
101
|
+
*/
|
|
102
|
+
equations: Graph2DEquation[];
|
|
103
|
+
centerX: number;
|
|
104
|
+
centerY: number;
|
|
105
|
+
xSpan: number;
|
|
106
|
+
ySpan: number;
|
|
107
|
+
/** The paper. Flags snap at the end of a tween; the rest lerp. */
|
|
108
|
+
grid: Graph2DGrid;
|
|
109
|
+
background: Color;
|
|
110
|
+
backgroundOpacity: number;
|
|
111
|
+
lineWidth: number;
|
|
112
|
+
/**
|
|
113
|
+
* The node's one piece of memoisation, and it earns its keep: a graph that is
|
|
114
|
+
* holding still re-samples nothing at all. See `curve-cache.ts`.
|
|
115
|
+
*/
|
|
116
|
+
private readonly runs;
|
|
117
|
+
/** Where the node is looking, as the four props hold it. */
|
|
118
|
+
private get view();
|
|
119
|
+
/**
|
|
120
|
+
* Confines every mark to the node's own box — its own drawing as well as its
|
|
121
|
+
* children's, which is why this is `clipPathSelf` and not `clipSelf`.
|
|
122
|
+
*
|
|
123
|
+
* Belt and braces over the geometric clipping in `curve.ts` rather than a
|
|
124
|
+
* substitute for it: that clipping is what keeps astronomical coordinates away
|
|
125
|
+
* from the rasterizer, and this is what stops a stroke's half-width and its
|
|
126
|
+
* round joins from painting a hair outside the frame at the edges.
|
|
127
|
+
*/
|
|
128
|
+
protected clipPathSelf(): Clip | null;
|
|
129
|
+
protected renderSelf(ctx: RenderContext2D): void;
|
|
130
|
+
/**
|
|
131
|
+
* The paper, the two orders of ruling and the two axes — one rect, one shader,
|
|
132
|
+
* one draw, whatever the zoom.
|
|
133
|
+
*
|
|
134
|
+
* This was six hundred filled rects and three draws, and the rect count is the
|
|
135
|
+
* part that mattered: it changed as the ladder stepped, and the renderer keys
|
|
136
|
+
* its geometry cache by shape *index*, so a grid that gained a line invalidated
|
|
137
|
+
* every curve behind it. One shape means the curves' keys never move. See
|
|
138
|
+
* `plane-fill.ts` for why the built-in grid fill can't do this job and for what
|
|
139
|
+
* the phase uniforms are protecting against.
|
|
140
|
+
*/
|
|
141
|
+
private drawPlane;
|
|
142
|
+
/**
|
|
143
|
+
* The numbers along each axis.
|
|
144
|
+
*
|
|
145
|
+
* Pinned to the frame when their axis has left it, which is the behaviour
|
|
146
|
+
* every map and every grapher has and the reason it is worth the arithmetic:
|
|
147
|
+
* pan far enough that the origin is off screen and a graph whose numbers went
|
|
148
|
+
* with it is a graph you can no longer read. The row also flips to the other
|
|
149
|
+
* side of its axis when there is no room on the near one, so a view sitting
|
|
150
|
+
* just above the x-axis still numbers it.
|
|
151
|
+
*/
|
|
152
|
+
private drawLabels;
|
|
153
|
+
/**
|
|
154
|
+
* The curves themselves, one stroked polyline per run.
|
|
155
|
+
*
|
|
156
|
+
* One `draw` per run rather than one per curve, and that is not an
|
|
157
|
+
* optimisation to undo: shapes chained before a `stroke()` are combined into a
|
|
158
|
+
* single surface, and stroking a union of disjoint open paths paints nothing —
|
|
159
|
+
* so a curve that leaves the frame and comes back would lose *both* halves.
|
|
160
|
+
* `ChartLine` learned the same thing about its spotlight runs.
|
|
161
|
+
*/
|
|
162
|
+
private drawCurves;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=graph2d.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph2d.d.ts","sourceRoot":"","sources":["../../src/graph2d/graph2d.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,IAAI,EAGJ,MAAM,EAIN,KAAK,KAAK,EACV,KAAK,WAAW,EAEhB,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAA;AAsC3B,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,KAAK,EAAE,KAAK,CAAA;IACZ,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mFAAmF;IACnF,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,oDAAoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,KAAK,CAAA;IACjB,cAAc,CAAC,EAAE,KAAK,CAAA;IACtB,SAAS,CAAC,EAAE,KAAK,CAAA;IACjB,UAAU,CAAC,EAAE,KAAK,CAAA;IAClB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,iFAAiF;IACjF,SAAS,EAAE,eAAe,EAAE,CAAA;IAC5B,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAA;IACf,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAA;IACf,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,WAAW,CAAA;IACjB,0BAA0B;IAC1B,UAAU,EAAE,KAAK,CAAA;IACjB,qFAAqF;IACrF,iBAAiB,EAAE,MAAM,CAAA;IACzB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAA;CAClB;AAiED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAea,OAAQ,SAAQ,MAAM,CAAC,YAAY,CAAC;IAC/C;;;;;;;;;;OAUG;IAEK,SAAS,EAAE,eAAe,EAAE,CAAA;IAEF,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IAE/C,kEAAkE;IAE1D,IAAI,EAAE,WAAW,CAAA;IAGjB,UAAU,EAAE,KAAK,CAAA;IACS,iBAAiB,EAAE,MAAM,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IAEnD;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAuB;IAE5C,4DAA4D;IAC5D,OAAO,KAAK,IAAI,GAOf;IAED;;;;;;;;OAQG;cACgB,YAAY,IAAI,IAAI,GAAG,IAAI;cAS3B,UAAU,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IA2BzD;;;;;;;;;;OAUG;IACH,OAAO,CAAC,SAAS;IAmCjB;;;;;;;;;OASG;IACH,OAAO,CAAC,UAAU;IAuElB;;;;;;;;OAQG;IACH,OAAO,CAAC,UAAU;CAsDnB"}
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { node, Clip, Fills, Graphics2D, Node2D, clamp, lerpNumber, property, } from "@motionscript/core";
|
|
8
|
+
import { sampleBudget } from "./curve.js";
|
|
9
|
+
import { CurveRunsCache, runsKeyOf } from "./curve-cache.js";
|
|
10
|
+
import { formatTick, inflate, labelLadder, labelWidthEm, planeBox, resolvePlane, tickLadder, toPx, toPy, } from "./plane.js";
|
|
11
|
+
import { PLANE_SOURCE, planeUniforms, planeVisible, } from "./plane-fill.js";
|
|
12
|
+
import { compileCurveCached, lerpColor, lerpCurves, resolveColor, snapFlag, snapValue, } from "./shared.js";
|
|
13
|
+
/**
|
|
14
|
+
* The grid a node with nothing said about it draws — the same dark palette the
|
|
15
|
+
* 3D graph opens on, so the two read as one family when they share a scene.
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_GRID = {
|
|
18
|
+
showGrid: true,
|
|
19
|
+
showMinorGrid: true,
|
|
20
|
+
showAxes: true,
|
|
21
|
+
showLabels: true,
|
|
22
|
+
colorGrid: resolveColor("#374151"),
|
|
23
|
+
colorMinorGrid: resolveColor("#1f2937"),
|
|
24
|
+
colorAxis: resolveColor("#6b7280"),
|
|
25
|
+
colorLabel: resolveColor("#9ca3af"),
|
|
26
|
+
lineWidth: 2,
|
|
27
|
+
axisWidth: 3,
|
|
28
|
+
fontSize: 22,
|
|
29
|
+
fontFamily: "",
|
|
30
|
+
};
|
|
31
|
+
/** How far a curve may run past the box before it is cut away, in stroke widths. */
|
|
32
|
+
const CLIP_MARGIN = 1;
|
|
33
|
+
/**
|
|
34
|
+
* How close two stacked numbers may sit before the ladder steps up, in font
|
|
35
|
+
* sizes.
|
|
36
|
+
*
|
|
37
|
+
* Only the vertical one lives here. Across, how close two numbers may sit
|
|
38
|
+
* depends on how *long* they are — five characters near the origin, fourteen at
|
|
39
|
+
* a deep zoom — so that ladder is built by `labelLadder`, which measures what it
|
|
40
|
+
* just produced and widens itself once if it has to. Down, a number is one line
|
|
41
|
+
* whatever it says.
|
|
42
|
+
*/
|
|
43
|
+
const LABEL_PITCH_Y = 2.6;
|
|
44
|
+
const LABEL_GAP = 0.45;
|
|
45
|
+
/**
|
|
46
|
+
* Narrowest the box a number is aligned within may be, in font sizes.
|
|
47
|
+
*
|
|
48
|
+
* Deliberately wider than the numbers an ordinary view carries, so a
|
|
49
|
+
* right-aligned `-1200` and a right-aligned `5` end in the same place. It only
|
|
50
|
+
* ever grows from here — see `labelWidthEm`.
|
|
51
|
+
*/
|
|
52
|
+
const LABEL_BOX = 6;
|
|
53
|
+
/**
|
|
54
|
+
* A 2D function grapher — any number of `y = f(x)` curves over a ruled plane,
|
|
55
|
+
* with numbered axes.
|
|
56
|
+
*
|
|
57
|
+
* <Graph2D
|
|
58
|
+
* width={960} height={540}
|
|
59
|
+
* equations={[{ id: 1, expression: "log(x)", color: "#3B82F6" }]}
|
|
60
|
+
* />
|
|
61
|
+
*
|
|
62
|
+
* The sibling of {@link Graph3D}, and deliberately built the same way: the same
|
|
63
|
+
* little expression language, the same equation tiles, the same match-by-id
|
|
64
|
+
* tween over the list. What it does *not* share is the hard part, which is not
|
|
65
|
+
* the maths but the drawing — see `curve.ts`, which exists because the obvious
|
|
66
|
+
* sampling loop cuts `log(x)` off in mid-air, silently drops `1/x` entirely on a
|
|
67
|
+
* fixed-point rasterizer, and puts a visible polygonal kink wherever a curve
|
|
68
|
+
* turns sharply.
|
|
69
|
+
*
|
|
70
|
+
* Every prop is a `@property`, so a constant, a `() => signal()` binding, and
|
|
71
|
+
* `set()`/`to()` on a ref all work and agree. The non-scalar props each declare
|
|
72
|
+
* a `mapper` (loose authored shape → one canonical internal shape) and a `tween`
|
|
73
|
+
* (how that shape interpolates) — without the tween, `to({ grid: … })` would hold
|
|
74
|
+
* the old value and snap at the end, which is the standard trap for an
|
|
75
|
+
* object-valued attribute.
|
|
76
|
+
*
|
|
77
|
+
* **The view is four plain numbers, not a transform.** `centerX`/`centerY` and
|
|
78
|
+
* `xSpan`/`ySpan` are each independently tweenable, which is what makes "pan
|
|
79
|
+
* across while zooming in" one command and what keeps every frame reproducible
|
|
80
|
+
* under scrubbing and export. The canvas's pan/zoom gesture writes exactly these
|
|
81
|
+
* four, so a view flown by hand is a view a `to` can animate afterwards.
|
|
82
|
+
*/
|
|
83
|
+
let Graph2D = class Graph2D extends Node2D {
|
|
84
|
+
/**
|
|
85
|
+
* The node's one piece of memoisation, and it earns its keep: a graph that is
|
|
86
|
+
* holding still re-samples nothing at all. See `curve-cache.ts`.
|
|
87
|
+
*/
|
|
88
|
+
runs = new CurveRunsCache();
|
|
89
|
+
/** Where the node is looking, as the four props hold it. */
|
|
90
|
+
get view() {
|
|
91
|
+
return {
|
|
92
|
+
centerX: this.centerX,
|
|
93
|
+
centerY: this.centerY,
|
|
94
|
+
xSpan: this.xSpan,
|
|
95
|
+
ySpan: this.ySpan,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Confines every mark to the node's own box — its own drawing as well as its
|
|
100
|
+
* children's, which is why this is `clipPathSelf` and not `clipSelf`.
|
|
101
|
+
*
|
|
102
|
+
* Belt and braces over the geometric clipping in `curve.ts` rather than a
|
|
103
|
+
* substitute for it: that clipping is what keeps astronomical coordinates away
|
|
104
|
+
* from the rasterizer, and this is what stops a stroke's half-width and its
|
|
105
|
+
* round joins from painting a hair outside the frame at the edges.
|
|
106
|
+
*/
|
|
107
|
+
clipPathSelf() {
|
|
108
|
+
const width = this.layoutBounds.width;
|
|
109
|
+
const height = this.layoutBounds.height;
|
|
110
|
+
if (width <= 0 || height <= 0)
|
|
111
|
+
return null;
|
|
112
|
+
return new Clip().rect({ x: 0, y: 0, width, height });
|
|
113
|
+
}
|
|
114
|
+
// ---- Drawing ------------------------------------------------------------
|
|
115
|
+
renderSelf(ctx) {
|
|
116
|
+
const width = this.layoutBounds.width;
|
|
117
|
+
const height = this.layoutBounds.height;
|
|
118
|
+
if (width <= 0 || height <= 0)
|
|
119
|
+
return;
|
|
120
|
+
const plane = resolvePlane(this.view, width, height);
|
|
121
|
+
const grid = this.grid;
|
|
122
|
+
const box = planeBox(plane);
|
|
123
|
+
// One ladder per axis, chosen from the *scale* rather than from a fixed
|
|
124
|
+
// count: what decides how finely the plane is ruled is how close two numbers
|
|
125
|
+
// would sit on screen, which is the only thing that stays true as the view is
|
|
126
|
+
// flown around.
|
|
127
|
+
const xTicks = labelLadder(plane.xMin, plane.xMax, plane.scaleX, grid.fontSize);
|
|
128
|
+
const yTicks = tickLadder(plane.yMin, plane.yMax, plane.scaleY, grid.fontSize * LABEL_PITCH_Y);
|
|
129
|
+
this.drawPlane(ctx, plane, grid, xTicks, yTicks);
|
|
130
|
+
if (grid.showLabels)
|
|
131
|
+
this.drawLabels(ctx, plane, box, grid, xTicks, yTicks);
|
|
132
|
+
this.drawCurves(ctx, plane, box);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The paper, the two orders of ruling and the two axes — one rect, one shader,
|
|
136
|
+
* one draw, whatever the zoom.
|
|
137
|
+
*
|
|
138
|
+
* This was six hundred filled rects and three draws, and the rect count is the
|
|
139
|
+
* part that mattered: it changed as the ladder stepped, and the renderer keys
|
|
140
|
+
* its geometry cache by shape *index*, so a grid that gained a line invalidated
|
|
141
|
+
* every curve behind it. One shape means the curves' keys never move. See
|
|
142
|
+
* `plane-fill.ts` for why the built-in grid fill can't do this job and for what
|
|
143
|
+
* the phase uniforms are protecting against.
|
|
144
|
+
*/
|
|
145
|
+
drawPlane(ctx, plane, grid, xTicks, yTicks) {
|
|
146
|
+
const style = {
|
|
147
|
+
paper: this.background,
|
|
148
|
+
paperOpacity: clamp01(this.backgroundOpacity),
|
|
149
|
+
showGrid: grid.showGrid,
|
|
150
|
+
showMinorGrid: grid.showMinorGrid,
|
|
151
|
+
showAxes: grid.showAxes,
|
|
152
|
+
colorGrid: grid.colorGrid,
|
|
153
|
+
colorMinorGrid: grid.colorMinorGrid,
|
|
154
|
+
colorAxis: grid.colorAxis,
|
|
155
|
+
lineWidth: grid.lineWidth,
|
|
156
|
+
axisWidth: grid.axisWidth,
|
|
157
|
+
};
|
|
158
|
+
// Nothing to paint at all — clear paper, no rulings, no axes — is a whole
|
|
159
|
+
// fragment pass over the box that would come out transparent.
|
|
160
|
+
if (!planeVisible(style))
|
|
161
|
+
return;
|
|
162
|
+
ctx.draw(new Graphics2D()
|
|
163
|
+
.rect({ x: 0, y: 0, width: plane.width, height: plane.height })
|
|
164
|
+
.fill(Fills.shader(PLANE_SOURCE, {
|
|
165
|
+
uniforms: planeUniforms(plane, style, xTicks, yTicks),
|
|
166
|
+
coords: "local",
|
|
167
|
+
})));
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The numbers along each axis.
|
|
171
|
+
*
|
|
172
|
+
* Pinned to the frame when their axis has left it, which is the behaviour
|
|
173
|
+
* every map and every grapher has and the reason it is worth the arithmetic:
|
|
174
|
+
* pan far enough that the origin is off screen and a graph whose numbers went
|
|
175
|
+
* with it is a graph you can no longer read. The row also flips to the other
|
|
176
|
+
* side of its axis when there is no room on the near one, so a view sitting
|
|
177
|
+
* just above the x-axis still numbers it.
|
|
178
|
+
*/
|
|
179
|
+
drawLabels(ctx, plane, box, grid, xTicks, yTicks) {
|
|
180
|
+
const labels = new Graphics2D();
|
|
181
|
+
const gap = grid.fontSize * LABEL_GAP;
|
|
182
|
+
// Per axis, because the two ladders carry different numbers: a view can be
|
|
183
|
+
// ten units wide and a billionth of one tall.
|
|
184
|
+
const xBox = grid.fontSize * Math.max(LABEL_BOX, labelWidthEm(xTicks));
|
|
185
|
+
const yBox = grid.fontSize * Math.max(LABEL_BOX, labelWidthEm(yTicks));
|
|
186
|
+
const font = {
|
|
187
|
+
fontSize: grid.fontSize,
|
|
188
|
+
// An empty family is dropped rather than passed on: the renderer's font
|
|
189
|
+
// provider holds only what the manifest registered, so asking it for a
|
|
190
|
+
// family called `""` shapes against nothing and paints no glyphs. Left
|
|
191
|
+
// unset it inherits the scene's own default face.
|
|
192
|
+
...(grid.fontFamily === "" ? {} : { fontFamily: grid.fontFamily }),
|
|
193
|
+
wrap: false,
|
|
194
|
+
};
|
|
195
|
+
// The x-axis row: under the axis when there is room beneath it, over it when
|
|
196
|
+
// there isn't.
|
|
197
|
+
const axisY = clamp(toPy(plane, 0), box.bottom, box.top);
|
|
198
|
+
const under = axisY - gap - grid.fontSize >= box.bottom;
|
|
199
|
+
const rowY = under ? axisY - gap - grid.fontSize / 2 : axisY + gap + grid.fontSize / 2;
|
|
200
|
+
for (const x of xTicks.major) {
|
|
201
|
+
labels.text({
|
|
202
|
+
...font,
|
|
203
|
+
text: formatTick(x, xTicks.step),
|
|
204
|
+
x: toPx(plane, x),
|
|
205
|
+
y: rowY,
|
|
206
|
+
width: xBox,
|
|
207
|
+
textAlign: "center",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
// The y-axis column: to the left of the axis when there is room, to the
|
|
211
|
+
// right when the axis is against the left edge.
|
|
212
|
+
// Room for a couple of characters, not for the whole alignment box: the box
|
|
213
|
+
// is deliberately wide (a right-aligned `-1200` and a right-aligned `5` end
|
|
214
|
+
// in the same place), and demanding all of it would push the column to the
|
|
215
|
+
// wrong side of an axis that has plenty of room for the numbers it holds.
|
|
216
|
+
const axisX = clamp(toPx(plane, 0), box.left, box.right);
|
|
217
|
+
const leftOf = axisX - gap - grid.fontSize * 1.5 >= box.left;
|
|
218
|
+
const columnX = leftOf ? axisX - gap - yBox / 2 : axisX + gap + yBox / 2;
|
|
219
|
+
// Zero belongs to the row, not the column: drawn in both it would be two
|
|
220
|
+
// numbers stacked on the origin. Skipped only when the row is actually
|
|
221
|
+
// showing it, so a view with the y-axis off screen still numbers its own 0.
|
|
222
|
+
const rowHasZero = plane.xMin <= 0 && 0 <= plane.xMax;
|
|
223
|
+
for (const y of yTicks.major) {
|
|
224
|
+
if (y === 0 && rowHasZero)
|
|
225
|
+
continue;
|
|
226
|
+
labels.text({
|
|
227
|
+
...font,
|
|
228
|
+
text: formatTick(y, yTicks.step),
|
|
229
|
+
x: columnX,
|
|
230
|
+
y: toPy(plane, y),
|
|
231
|
+
width: yBox,
|
|
232
|
+
textAlign: leftOf ? "right" : "left",
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
ctx.draw(labels.fill(Fills.color(grid.colorLabel)));
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The curves themselves, one stroked polyline per run.
|
|
239
|
+
*
|
|
240
|
+
* One `draw` per run rather than one per curve, and that is not an
|
|
241
|
+
* optimisation to undo: shapes chained before a `stroke()` are combined into a
|
|
242
|
+
* single surface, and stroking a union of disjoint open paths paints nothing —
|
|
243
|
+
* so a curve that leaves the frame and comes back would lose *both* halves.
|
|
244
|
+
* `ChartLine` learned the same thing about its spotlight runs.
|
|
245
|
+
*/
|
|
246
|
+
drawCurves(ctx, plane, box) {
|
|
247
|
+
const equations = this.equations;
|
|
248
|
+
const weight = Math.max(0.1, this.lineWidth);
|
|
249
|
+
const clip = inflate(box, weight * CLIP_MARGIN);
|
|
250
|
+
const key = runsKeyOf(plane, clip);
|
|
251
|
+
// What actually has to be sampled, counted before any of it is — so a frame
|
|
252
|
+
// where five of six curves are unchanged hands the whole evaluation budget
|
|
253
|
+
// to the one that moved rather than reserving five sixths of it for work
|
|
254
|
+
// that is already done.
|
|
255
|
+
const live = [];
|
|
256
|
+
let misses = 0;
|
|
257
|
+
for (const equation of equations) {
|
|
258
|
+
// Fully faded out (hidden, or the far end of a cross-fade): sample nothing.
|
|
259
|
+
if (equation.opacity <= 0.001)
|
|
260
|
+
continue;
|
|
261
|
+
live.push(equation.id);
|
|
262
|
+
if (!this.runs.holds(equation.id, equation.sample, key))
|
|
263
|
+
misses++;
|
|
264
|
+
}
|
|
265
|
+
const budget = misses > 0 ? sampleBudget(misses) : undefined;
|
|
266
|
+
this.runs.retain(live);
|
|
267
|
+
for (const equation of equations) {
|
|
268
|
+
if (equation.opacity <= 0.001)
|
|
269
|
+
continue;
|
|
270
|
+
const stroke = {
|
|
271
|
+
weight,
|
|
272
|
+
fill: Fills.color(equation.color, {
|
|
273
|
+
opacity: clamp01(equation.opacity),
|
|
274
|
+
}),
|
|
275
|
+
// Round on both counts because this is a *curve*: a miter join on a
|
|
276
|
+
// sharp turn throws a spike, and a butt cap leaves a visible flat end
|
|
277
|
+
// wherever a run stops at a domain edge.
|
|
278
|
+
cap: "round",
|
|
279
|
+
join: "round",
|
|
280
|
+
};
|
|
281
|
+
const runs = this.runs.runs(equation.id, equation.sample, key, plane, clip, budget);
|
|
282
|
+
for (const run of runs) {
|
|
283
|
+
if (run.length < 2)
|
|
284
|
+
continue;
|
|
285
|
+
ctx.draw(new Graphics2D().line({ points: run, radius: 0 }).stroke(stroke));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
__decorate([
|
|
291
|
+
property({ default: [], mapper: resolveEquations, tween: lerpCurves })
|
|
292
|
+
], Graph2D.prototype, "equations", void 0);
|
|
293
|
+
__decorate([
|
|
294
|
+
property({ default: 0 })
|
|
295
|
+
], Graph2D.prototype, "centerX", void 0);
|
|
296
|
+
__decorate([
|
|
297
|
+
property({ default: 0 })
|
|
298
|
+
], Graph2D.prototype, "centerY", void 0);
|
|
299
|
+
__decorate([
|
|
300
|
+
property({ default: 20 })
|
|
301
|
+
], Graph2D.prototype, "xSpan", void 0);
|
|
302
|
+
__decorate([
|
|
303
|
+
property({ default: 0 })
|
|
304
|
+
], Graph2D.prototype, "ySpan", void 0);
|
|
305
|
+
__decorate([
|
|
306
|
+
property({ default: {}, mapper: resolveGrid, tween: lerpGrid })
|
|
307
|
+
], Graph2D.prototype, "grid", void 0);
|
|
308
|
+
__decorate([
|
|
309
|
+
property({ default: "#111827", mapper: resolveColor, tween: lerpColor })
|
|
310
|
+
], Graph2D.prototype, "background", void 0);
|
|
311
|
+
__decorate([
|
|
312
|
+
property({ default: 1 })
|
|
313
|
+
], Graph2D.prototype, "backgroundOpacity", void 0);
|
|
314
|
+
__decorate([
|
|
315
|
+
property({ default: 5 })
|
|
316
|
+
], Graph2D.prototype, "lineWidth", void 0);
|
|
317
|
+
Graph2D = __decorate([
|
|
318
|
+
node({
|
|
319
|
+
key: "graph2d",
|
|
320
|
+
parentKey: "node",
|
|
321
|
+
forkable: true,
|
|
322
|
+
layout: {
|
|
323
|
+
children: "freeform",
|
|
324
|
+
defaultWidthMode: "fixed",
|
|
325
|
+
defaultHeightMode: "fixed",
|
|
326
|
+
acceptsChildren: true,
|
|
327
|
+
},
|
|
328
|
+
seed: {
|
|
329
|
+
width: 960,
|
|
330
|
+
height: 540,
|
|
331
|
+
},
|
|
332
|
+
})
|
|
333
|
+
], Graph2D);
|
|
334
|
+
export { Graph2D };
|
|
335
|
+
// --- Mappers & tweens ------------------------------------------------------
|
|
336
|
+
/** Mapper for {@link Graph2D.equations}. */
|
|
337
|
+
function resolveEquations(input) {
|
|
338
|
+
if (!input)
|
|
339
|
+
return [];
|
|
340
|
+
const out = [];
|
|
341
|
+
for (const equation of input) {
|
|
342
|
+
const sample = compileCurveCached(equation.expression);
|
|
343
|
+
if (!sample)
|
|
344
|
+
continue; // unparseable or empty — drop it, keep the rest
|
|
345
|
+
out.push({
|
|
346
|
+
id: String(equation.id),
|
|
347
|
+
sample,
|
|
348
|
+
color: resolveColor(equation.color),
|
|
349
|
+
opacity: equation.visible === false ? 0 : (equation.opacity ?? 1),
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return out;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Mapper for {@link Graph2D.grid}.
|
|
356
|
+
*
|
|
357
|
+
* Merges onto `previous` rather than onto the bare defaults, which is what makes
|
|
358
|
+
* a partial write mean "change this much and leave the rest". It matters most on
|
|
359
|
+
* a `to` command, whose target states only the fields that command touches: a
|
|
360
|
+
* tween that dims the grid must not silently hand the axis numbers back to their
|
|
361
|
+
* default size on the way.
|
|
362
|
+
*/
|
|
363
|
+
function resolveGrid(input, previous) {
|
|
364
|
+
const base = previous ?? DEFAULT_GRID;
|
|
365
|
+
return {
|
|
366
|
+
showGrid: input?.showGrid ?? base.showGrid,
|
|
367
|
+
showMinorGrid: input?.showMinorGrid ?? base.showMinorGrid,
|
|
368
|
+
showAxes: input?.showAxes ?? base.showAxes,
|
|
369
|
+
showLabels: input?.showLabels ?? base.showLabels,
|
|
370
|
+
colorGrid: colorOr(input?.colorGrid, base.colorGrid),
|
|
371
|
+
colorMinorGrid: colorOr(input?.colorMinorGrid, base.colorMinorGrid),
|
|
372
|
+
colorAxis: colorOr(input?.colorAxis, base.colorAxis),
|
|
373
|
+
colorLabel: colorOr(input?.colorLabel, base.colorLabel),
|
|
374
|
+
lineWidth: input?.lineWidth ?? base.lineWidth,
|
|
375
|
+
axisWidth: input?.axisWidth ?? base.axisWidth,
|
|
376
|
+
fontSize: input?.fontSize ?? base.fontSize,
|
|
377
|
+
fontFamily: input?.fontFamily ?? base.fontFamily,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function colorOr(input, fallback) {
|
|
381
|
+
return input === undefined ? fallback : resolveColor(input);
|
|
382
|
+
}
|
|
383
|
+
/** Tween for {@link Graph2D.grid}. */
|
|
384
|
+
function lerpGrid(from, to, t) {
|
|
385
|
+
return {
|
|
386
|
+
showGrid: snapFlag(from.showGrid, to.showGrid, t),
|
|
387
|
+
showMinorGrid: snapFlag(from.showMinorGrid, to.showMinorGrid, t),
|
|
388
|
+
showAxes: snapFlag(from.showAxes, to.showAxes, t),
|
|
389
|
+
showLabels: snapFlag(from.showLabels, to.showLabels, t),
|
|
390
|
+
colorGrid: lerpColor(from.colorGrid, to.colorGrid, t),
|
|
391
|
+
colorMinorGrid: lerpColor(from.colorMinorGrid, to.colorMinorGrid, t),
|
|
392
|
+
colorAxis: lerpColor(from.colorAxis, to.colorAxis, t),
|
|
393
|
+
colorLabel: lerpColor(from.colorLabel, to.colorLabel, t),
|
|
394
|
+
lineWidth: lerpNumber(from.lineWidth, to.lineWidth, t),
|
|
395
|
+
axisWidth: lerpNumber(from.axisWidth, to.axisWidth, t),
|
|
396
|
+
fontSize: lerpNumber(from.fontSize, to.fontSize, t),
|
|
397
|
+
// A face has no halfway point, so it changes once the tween lands.
|
|
398
|
+
fontFamily: snapValue(from.fontFamily, to.fontFamily, t),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function clamp01(value) {
|
|
402
|
+
return Number.isFinite(value) ? clamp(value, 0, 1) : 1;
|
|
403
|
+
}
|
|
404
|
+
//# sourceMappingURL=graph2d.js.map
|