dsviz 0.0.0-stage → 2.0.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.
Files changed (215) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -2
  3. package/dist/camera.d.ts +25 -0
  4. package/dist/camera.js +27 -0
  5. package/dist/camera.js.map +7 -0
  6. package/dist/connector.d.ts +65 -0
  7. package/dist/connector.js +108 -0
  8. package/dist/connector.js.map +7 -0
  9. package/dist/describe.d.ts +44 -0
  10. package/dist/describe.js +131 -0
  11. package/dist/describe.js.map +7 -0
  12. package/dist/ds/array.d.ts +51 -0
  13. package/dist/ds/array.js +62 -0
  14. package/dist/ds/array.js.map +7 -0
  15. package/dist/ds/common.d.ts +9 -0
  16. package/dist/ds/common.js +10 -0
  17. package/dist/ds/common.js.map +7 -0
  18. package/dist/ds/graph.d.ts +39 -0
  19. package/dist/ds/graph.js +57 -0
  20. package/dist/ds/graph.js.map +7 -0
  21. package/dist/ds/grid.d.ts +30 -0
  22. package/dist/ds/grid.js +41 -0
  23. package/dist/ds/grid.js.map +7 -0
  24. package/dist/ds/index.d.ts +8 -0
  25. package/dist/ds/index.js +9 -0
  26. package/dist/ds/index.js.map +7 -0
  27. package/dist/ds/linkedList.d.ts +46 -0
  28. package/dist/ds/linkedList.js +68 -0
  29. package/dist/ds/linkedList.js.map +7 -0
  30. package/dist/ds/pointer.d.ts +23 -0
  31. package/dist/ds/pointer.js +35 -0
  32. package/dist/ds/pointer.js.map +7 -0
  33. package/dist/ds/stackQueue.d.ts +5 -0
  34. package/dist/ds/stackQueue.js +49 -0
  35. package/dist/ds/stackQueue.js.map +7 -0
  36. package/dist/ds/tree.d.ts +53 -0
  37. package/dist/ds/tree.js +87 -0
  38. package/dist/ds/tree.js.map +7 -0
  39. package/dist/easing.d.ts +14 -0
  40. package/dist/easing.js +21 -0
  41. package/dist/easing.js.map +7 -0
  42. package/dist/fx.d.ts +21 -0
  43. package/dist/fx.js +27 -0
  44. package/dist/fx.js.map +7 -0
  45. package/dist/geom/index.d.ts +2 -0
  46. package/dist/geom/index.js +3 -0
  47. package/dist/geom/index.js.map +7 -0
  48. package/dist/geom/math.d.ts +34 -0
  49. package/dist/geom/math.js +74 -0
  50. package/dist/geom/math.js.map +7 -0
  51. package/dist/geom/shapes.d.ts +69 -0
  52. package/dist/geom/shapes.js +105 -0
  53. package/dist/geom/shapes.js.map +7 -0
  54. package/dist/handle.d.ts +30 -0
  55. package/dist/handle.js +13 -0
  56. package/dist/handle.js.map +7 -0
  57. package/dist/index.d.ts +36 -0
  58. package/dist/index.js +35 -0
  59. package/dist/index.js.map +7 -0
  60. package/dist/mathtext.d.ts +29 -0
  61. package/dist/mathtext.js +142 -0
  62. package/dist/mathtext.js.map +7 -0
  63. package/dist/mem/index.d.ts +2 -0
  64. package/dist/mem/index.js +3 -0
  65. package/dist/mem/index.js.map +7 -0
  66. package/dist/mem/memory.d.ts +47 -0
  67. package/dist/mem/memory.js +41 -0
  68. package/dist/mem/memory.js.map +7 -0
  69. package/dist/mem/stackHeap.d.ts +48 -0
  70. package/dist/mem/stackHeap.js +76 -0
  71. package/dist/mem/stackHeap.js.map +7 -0
  72. package/dist/path.d.ts +23 -0
  73. package/dist/path.js +49 -0
  74. package/dist/path.js.map +7 -0
  75. package/dist/player.d.ts +49 -0
  76. package/dist/player.js +100 -0
  77. package/dist/player.js.map +7 -0
  78. package/dist/plot/index.d.ts +2 -0
  79. package/dist/plot/index.js +3 -0
  80. package/dist/plot/index.js.map +7 -0
  81. package/dist/plot/marks.d.ts +120 -0
  82. package/dist/plot/marks.js +200 -0
  83. package/dist/plot/marks.js.map +7 -0
  84. package/dist/plot/plane.d.ts +45 -0
  85. package/dist/plot/plane.js +41 -0
  86. package/dist/plot/plane.js.map +7 -0
  87. package/dist/prob/bars.d.ts +67 -0
  88. package/dist/prob/bars.js +82 -0
  89. package/dist/prob/bars.js.map +7 -0
  90. package/dist/prob/index.d.ts +3 -0
  91. package/dist/prob/index.js +4 -0
  92. package/dist/prob/index.js.map +7 -0
  93. package/dist/prob/tree.d.ts +39 -0
  94. package/dist/prob/tree.js +57 -0
  95. package/dist/prob/tree.js.map +7 -0
  96. package/dist/prob/venn.d.ts +38 -0
  97. package/dist/prob/venn.js +89 -0
  98. package/dist/prob/venn.js.map +7 -0
  99. package/dist/react/Diagram.d.ts +47 -0
  100. package/dist/react/Diagram.js +41 -0
  101. package/dist/react/Diagram.js.map +7 -0
  102. package/dist/react/MathText.d.ts +9 -0
  103. package/dist/react/MathText.js +15 -0
  104. package/dist/react/MathText.js.map +7 -0
  105. package/dist/react/controls-styles.d.ts +9 -0
  106. package/dist/react/controls-styles.js +109 -0
  107. package/dist/react/controls-styles.js.map +7 -0
  108. package/dist/react/controls.d.ts +84 -0
  109. package/dist/react/controls.js +146 -0
  110. package/dist/react/controls.js.map +7 -0
  111. package/dist/react/hooks.d.ts +48 -0
  112. package/dist/react/hooks.js +67 -0
  113. package/dist/react/hooks.js.map +7 -0
  114. package/dist/react/index.d.ts +4 -0
  115. package/dist/react/index.js +5 -0
  116. package/dist/react/index.js.map +7 -0
  117. package/dist/render.d.ts +61 -0
  118. package/dist/render.js +294 -0
  119. package/dist/render.js.map +7 -0
  120. package/dist/scene.d.ts +138 -0
  121. package/dist/scene.js +94 -0
  122. package/dist/scene.js.map +7 -0
  123. package/dist/sched/gantt.d.ts +49 -0
  124. package/dist/sched/gantt.js +65 -0
  125. package/dist/sched/gantt.js.map +7 -0
  126. package/dist/sched/index.d.ts +2 -0
  127. package/dist/sched/index.js +3 -0
  128. package/dist/sched/index.js.map +7 -0
  129. package/dist/sched/simulate.d.ts +55 -0
  130. package/dist/sched/simulate.js +78 -0
  131. package/dist/sched/simulate.js.map +7 -0
  132. package/dist/static.d.ts +8 -0
  133. package/dist/static.js +26 -0
  134. package/dist/static.js.map +7 -0
  135. package/dist/styles.d.ts +3 -0
  136. package/dist/styles.js +241 -0
  137. package/dist/styles.js.map +7 -0
  138. package/dist/sys/architecture.d.ts +104 -0
  139. package/dist/sys/architecture.js +167 -0
  140. package/dist/sys/architecture.js.map +7 -0
  141. package/dist/sys/index.d.ts +1 -0
  142. package/dist/sys/index.js +2 -0
  143. package/dist/sys/index.js.map +7 -0
  144. package/dist/themes.d.ts +13 -0
  145. package/dist/themes.js +8 -0
  146. package/dist/themes.js.map +7 -0
  147. package/dist/uml/classDiagram.d.ts +72 -0
  148. package/dist/uml/classDiagram.js +84 -0
  149. package/dist/uml/classDiagram.js.map +7 -0
  150. package/dist/uml/index.d.ts +3 -0
  151. package/dist/uml/index.js +4 -0
  152. package/dist/uml/index.js.map +7 -0
  153. package/dist/uml/sequence.d.ts +58 -0
  154. package/dist/uml/sequence.js +121 -0
  155. package/dist/uml/sequence.js.map +7 -0
  156. package/dist/uml/stateMachine.d.ts +40 -0
  157. package/dist/uml/stateMachine.js +89 -0
  158. package/dist/uml/stateMachine.js.map +7 -0
  159. package/dist/vec.d.ts +21 -0
  160. package/dist/vec.js +24 -0
  161. package/dist/vec.js.map +7 -0
  162. package/package.json +93 -4
  163. package/src/camera.ts +50 -0
  164. package/src/connector.ts +182 -0
  165. package/src/describe.ts +181 -0
  166. package/src/ds/array.ts +108 -0
  167. package/src/ds/common.ts +16 -0
  168. package/src/ds/graph.ts +104 -0
  169. package/src/ds/grid.ts +70 -0
  170. package/src/ds/index.ts +8 -0
  171. package/src/ds/linkedList.ts +117 -0
  172. package/src/ds/pointer.ts +55 -0
  173. package/src/ds/stackQueue.ts +59 -0
  174. package/src/ds/tree.ts +139 -0
  175. package/src/easing.ts +24 -0
  176. package/src/fx.ts +46 -0
  177. package/src/geom/index.ts +2 -0
  178. package/src/geom/math.ts +85 -0
  179. package/src/geom/shapes.ts +158 -0
  180. package/src/handle.ts +40 -0
  181. package/src/index.ts +37 -0
  182. package/src/mathtext.ts +139 -0
  183. package/src/mem/index.ts +2 -0
  184. package/src/mem/memory.ts +85 -0
  185. package/src/mem/stackHeap.ts +127 -0
  186. package/src/path.ts +68 -0
  187. package/src/player.ts +153 -0
  188. package/src/plot/index.ts +2 -0
  189. package/src/plot/marks.ts +298 -0
  190. package/src/plot/plane.ts +82 -0
  191. package/src/prob/bars.ts +132 -0
  192. package/src/prob/index.ts +3 -0
  193. package/src/prob/tree.ts +94 -0
  194. package/src/prob/venn.ts +144 -0
  195. package/src/react/Diagram.tsx +97 -0
  196. package/src/react/MathText.tsx +21 -0
  197. package/src/react/controls-styles.ts +111 -0
  198. package/src/react/controls.tsx +297 -0
  199. package/src/react/hooks.ts +112 -0
  200. package/src/react/index.ts +4 -0
  201. package/src/render.ts +445 -0
  202. package/src/scene.ts +238 -0
  203. package/src/sched/gantt.ts +110 -0
  204. package/src/sched/index.ts +2 -0
  205. package/src/sched/simulate.ts +140 -0
  206. package/src/static.ts +35 -0
  207. package/src/styles.ts +272 -0
  208. package/src/sys/architecture.ts +257 -0
  209. package/src/sys/index.ts +1 -0
  210. package/src/themes.ts +26 -0
  211. package/src/uml/classDiagram.ts +145 -0
  212. package/src/uml/index.ts +3 -0
  213. package/src/uml/sequence.ts +218 -0
  214. package/src/uml/stateMachine.ts +133 -0
  215. package/src/vec.ts +28 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/mem/stackHeap.ts"],
4
+ "sourcesContent": ["/**\n * Python-Tutor-style memory pictures: stack frames on the left, heap objects\n * on the right, and an arrow for every reference.\n *\n * The thing this makes visible is ALIASING. `b = a` doesn't copy a list; it\n * adds a second arrow to the same object. `b.append(3)` then changes what `a`\n * points at too, and you can see why.\n *\n * Primitives (numbers, strings, None) are drawn inline in their slot. A\n * `{ ref: 'id' }` value draws a dot in the slot and an arrow to heap object `id`.\n */\nimport { Scene, SceneNode, Tone, arrow, circle, estimateTextWidth, rect, text } from '../scene';\nimport { V2, Vec } from '../vec';\n\nexport type MemValue = number | string | boolean | null | { ref: string };\n\nexport interface StackFrame {\n name: string;\n vars: ReadonlyArray<{ name: string; value: MemValue }>;\n}\n\nexport interface HeapObject {\n id: string;\n /** Shown above the object: 'list', 'tuple', 'dict', a class name\u2026 */\n kind: string;\n /** Sequence items, or [key, value] pairs for dicts / instances. */\n items: ReadonlyArray<MemValue> | ReadonlyArray<readonly [string, MemValue]>;\n}\n\nexport interface StackHeapOptions {\n id?: string;\n x?: number;\n y?: number;\n frameWidth?: number;\n /** x of the heap column. Default frameWidth + 110. */\n heapX?: number;\n rowHeight?: number;\n /** Tones keyed by `frame.var` (e.g. 'Global.a') or heap object id. */\n tones?: Record<string, Tone | undefined>;\n /** Heap layout: explicit positions per object id, relative to (heapX, y). */\n positions?: Record<string, Vec>;\n}\n\nexport interface StackHeapDiagram extends Scene {\n slot(frame: string, name: string): Vec;\n object(id: string): Vec;\n}\n\nconst show = (v: MemValue) => (v === null ? 'None' : typeof v === 'string' ? `\"${v}\"` : typeof v === 'boolean' ? (v ? 'True' : 'False') : String(v));\nconst isRef = (v: MemValue): v is { ref: string } => typeof v === 'object' && v !== null;\n\nexport function stackHeap(frames: readonly StackFrame[], heap: readonly HeapObject[], o: StackHeapOptions = {}): StackHeapDiagram {\n const id = o.id ?? 'mem';\n const x0 = o.x ?? 0, y0 = o.y ?? 0;\n const fw = o.frameWidth ?? 150;\n const hx = o.heapX ?? x0 + fw + 110;\n const rh = o.rowHeight ?? 28;\n const cellW = 40;\n const nodes: SceneNode[] = [];\n const arrows: SceneNode[] = [];\n const slots = new Map<string, Vec>();\n const objPos = new Map<string, Vec>();\n\n // \u2500\u2500 heap layout first (arrows need to know where objects are) \u2500\u2500\n let hy = y0 + 18;\n for (const obj of heap) {\n const p = o.positions?.[obj.id];\n const at = p ? V2(hx + p.x, y0 + p.y) : V2(hx, hy);\n objPos.set(obj.id, at);\n if (!p) hy += rh + 40;\n }\n\n // \u2500\u2500 stack frames \u2500\u2500\n let fy = y0;\n for (const f of frames) {\n const h = 26 + f.vars.length * rh + 8;\n nodes.push(rect({ id: `${id}:frame:${f.name}`, x: x0 + fw / 2, y: fy + h / 2, w: fw, h, rx: 6, role: 'frame' }));\n nodes.push(text({ id: `${id}:frame:${f.name}:t`, x: x0 + 10, y: fy + 13, text: f.name, size: 11, role: 'caption', anchor: 'start' }));\n f.vars.forEach((v, i) => {\n const cy = fy + 26 + i * rh + rh / 2;\n const key = `${f.name}.${v.name}`;\n const tone = o.tones?.[key];\n const slotX = x0 + fw - 34;\n slots.set(key, V2(slotX, cy));\n nodes.push(text({ id: `${id}:var:${key}`, x: slotX - 26, y: cy, text: v.name, size: 13, role: 'value', anchor: 'end', tone }));\n nodes.push(rect({ id: `${id}:slot:${key}`, x: slotX, y: cy, w: 40, h: rh - 6, rx: 4, tone }));\n if (isRef(v.value)) {\n nodes.push(circle({ id: `${id}:dot:${key}`, x: slotX, y: cy, r: 3.5, role: 'dot', tone }));\n const t = objPos.get(v.value.ref);\n if (t) arrows.push(refArrow(`${id}:ref:${key}`, V2(slotX, cy), V2(t.x - 4, t.y), tone));\n } else {\n nodes.push(text({ id: `${id}:val:${key}`, x: slotX, y: cy, text: show(v.value), size: 12, role: 'value', tone }));\n }\n });\n fy += h + 12;\n }\n\n // \u2500\u2500 heap objects \u2500\u2500\n for (const obj of heap) {\n const at = objPos.get(obj.id)!;\n const tone = o.tones?.[obj.id];\n nodes.push(text({ id: `${id}:obj:${obj.id}:kind`, x: at.x, y: at.y - rh / 2 - 9, text: obj.kind, size: 11, role: 'caption', anchor: 'start' }));\n const pairs = obj.items.length && Array.isArray(obj.items[0]);\n obj.items.forEach((item, i) => {\n const [k, v] = pairs ? (item as readonly [string, MemValue]) : [String(i), item as MemValue];\n const w = pairs ? Math.max(cellW, estimateTextWidth(k, 11) + 14) + cellW : cellW;\n const cx = at.x + i * (pairs ? w : cellW) + (pairs ? w : cellW) / 2;\n nodes.push(rect({ id: `${id}:obj:${obj.id}:${i}`, x: cx, y: at.y, w: pairs ? w : cellW, h: rh - 4, rx: 3, tone, enter: { x: cx + 20 } }));\n nodes.push(text({ id: `${id}:obj:${obj.id}:${i}:i`, x: pairs ? cx - w / 2 + 7 : cx, y: pairs ? at.y : at.y + rh / 2 + 7, text: k, size: pairs ? 11 : 9, role: 'index', anchor: pairs ? 'start' : 'middle' }));\n const vx = pairs ? cx + w / 2 - cellW / 2 : cx;\n if (isRef(v)) {\n nodes.push(circle({ id: `${id}:obj:${obj.id}:${i}:dot`, x: vx, y: at.y, r: 3.5, role: 'dot' }));\n const t = objPos.get(v.ref);\n if (t) arrows.push(refArrow(`${id}:ref:${obj.id}:${i}`, V2(vx, at.y), V2(t.x - 4, t.y), tone, true));\n } else nodes.push(text({ id: `${id}:obj:${obj.id}:${i}:v`, x: vx, y: at.y, text: show(v), size: 12, role: 'value', tone, enter: { x: vx + 20 } }));\n });\n if (!obj.items.length) nodes.push(rect({ id: `${id}:obj:${obj.id}:empty`, x: at.x + 14, y: at.y, w: 28, h: rh - 4, rx: 3, tone: 'ghost' }));\n }\n return { nodes: [...nodes, ...arrows], slot: (f, n) => slots.get(`${f}.${n}`) ?? V2(0, 0), object: (oid) => objPos.get(oid) ?? V2(0, 0) };\n}\n\nfunction refArrow(id: string, from: Vec, to: Vec, tone?: Tone, fromHeap = false): SceneNode {\n // bow arrows that go up or down so parallel references don't overlap\n const dy = to.y - from.y;\n const bend = fromHeap ? -40 : Math.abs(dy) < 4 ? 0 : Math.sign(dy) * -Math.min(30, Math.abs(dy) * 0.25);\n return arrow({ id, x1: from.x, y1: from.y, x2: to.x, y2: to.y, bend, width: 1.5, tone });\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/dist/path.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Path geometry: SVG path data for a point list, and resampling.
3
+ *
4
+ * Resampling is the interesting part. To tween shape A (n points) into shape B
5
+ * (m points) you need a point-to-point pairing. Walking along each shape by
6
+ * ARC LENGTH gives a natural one: "the point 30% of the way along" in A pairs
7
+ * with the point 30% of the way along in B. So we resample the old shape to m
8
+ * evenly spaced points and tween those.
9
+ */
10
+ import { NumericProps } from './scene';
11
+ type Pt = readonly [number, number];
12
+ /**
13
+ * `d` attribute for the points. Straight segments, or (smooth) a Catmull-Rom
14
+ * spline converted to cubic Béziers: for the segment P1→P2 the control points
15
+ * are P1 + (P2-P0)/6 and P2 - (P3-P1)/6. Endpoints reuse themselves as their
16
+ * missing neighbour (open) or wrap around (closed).
17
+ */
18
+ export declare function pathData(pts: ReadonlyArray<Pt>, closed: boolean, smooth: boolean): string;
19
+ /** `count` points spaced evenly by arc length along the polyline through `pts`. */
20
+ export declare function resample(pts: ReadonlyArray<Pt>, count: number, closed?: boolean): Array<[number, number]>;
21
+ /** Resample the points stored in a flat numeric bag (p0 p1 …), returning a bag with `to` points. */
22
+ export declare function resampleProps(cur: NumericProps, from: number, to: number, closed: boolean): NumericProps;
23
+ export {};
package/dist/path.js ADDED
@@ -0,0 +1,49 @@
1
+ const r = (v) => String(Math.round(v * 100) / 100);
2
+ function pathData(pts, closed, smooth) {
3
+ const n = pts.length;
4
+ if (n === 0) return "";
5
+ if (n === 1) return `M${r(pts[0][0])} ${r(pts[0][1])}`;
6
+ const at = (i) => closed ? pts[(i % n + n) % n] : pts[Math.max(0, Math.min(n - 1, i))];
7
+ let d = `M${r(pts[0][0])} ${r(pts[0][1])}`;
8
+ const segs = closed ? n : n - 1;
9
+ for (let i = 0; i < segs; i++) {
10
+ const p1 = at(i), p2 = at(i + 1);
11
+ if (!smooth) d += `L${r(p2[0])} ${r(p2[1])}`;
12
+ else {
13
+ const p0 = at(i - 1), p3 = at(i + 2);
14
+ d += `C${r(p1[0] + (p2[0] - p0[0]) / 6)} ${r(p1[1] + (p2[1] - p0[1]) / 6)} ${r(p2[0] - (p3[0] - p1[0]) / 6)} ${r(p2[1] - (p3[1] - p1[1]) / 6)} ${r(p2[0])} ${r(p2[1])}`;
15
+ }
16
+ }
17
+ return closed ? d + "Z" : d;
18
+ }
19
+ function resample(pts, count, closed = false) {
20
+ if (!pts.length || count <= 0) return [];
21
+ if (pts.length === 1) return Array.from({ length: count }, () => [pts[0][0], pts[0][1]]);
22
+ const ring = closed ? [...pts, pts[0]] : pts;
23
+ const cum = [0];
24
+ for (let i = 1; i < ring.length; i++) cum.push(cum[i - 1] + Math.hypot(ring[i][0] - ring[i - 1][0], ring[i][1] - ring[i - 1][1]));
25
+ const total = cum[cum.length - 1];
26
+ const out = [];
27
+ let seg = 1;
28
+ for (let k = 0; k < count; k++) {
29
+ const frac = count === 1 ? 0 : closed ? k / count : k / (count - 1);
30
+ const target = frac * total;
31
+ while (seg < cum.length - 1 && cum[seg] < target) seg++;
32
+ const span = cum[seg] - cum[seg - 1];
33
+ const t = span === 0 ? 0 : (target - cum[seg - 1]) / span;
34
+ out.push([ring[seg - 1][0] + (ring[seg][0] - ring[seg - 1][0]) * t, ring[seg - 1][1] + (ring[seg][1] - ring[seg - 1][1]) * t]);
35
+ }
36
+ return out;
37
+ }
38
+ function resampleProps(cur, from, to, closed) {
39
+ const pts = Array.from({ length: from }, (_, i) => [cur[`p${2 * i}`], cur[`p${2 * i + 1}`]]);
40
+ const out = {};
41
+ resample(pts, to, closed).forEach(([x, y], i) => (out[`p${2 * i}`] = x, out[`p${2 * i + 1}`] = y));
42
+ return out;
43
+ }
44
+ export {
45
+ pathData,
46
+ resample,
47
+ resampleProps
48
+ };
49
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/path.ts"],
4
+ "sourcesContent": ["/**\n * Path geometry: SVG path data for a point list, and resampling.\n *\n * Resampling is the interesting part. To tween shape A (n points) into shape B\n * (m points) you need a point-to-point pairing. Walking along each shape by\n * ARC LENGTH gives a natural one: \"the point 30% of the way along\" in A pairs\n * with the point 30% of the way along in B. So we resample the old shape to m\n * evenly spaced points and tween those.\n */\nimport { NumericProps } from './scene';\n\ntype Pt = readonly [number, number];\n\nconst r = (v: number) => String(Math.round(v * 100) / 100);\n\n/**\n * `d` attribute for the points. Straight segments, or (smooth) a Catmull-Rom\n * spline converted to cubic B\u00E9ziers: for the segment P1\u2192P2 the control points\n * are P1 + (P2-P0)/6 and P2 - (P3-P1)/6. Endpoints reuse themselves as their\n * missing neighbour (open) or wrap around (closed).\n */\nexport function pathData(pts: ReadonlyArray<Pt>, closed: boolean, smooth: boolean): string {\n const n = pts.length;\n if (n === 0) return '';\n if (n === 1) return `M${r(pts[0][0])} ${r(pts[0][1])}`;\n const at = (i: number): Pt => (closed ? pts[((i % n) + n) % n] : pts[Math.max(0, Math.min(n - 1, i))]);\n let d = `M${r(pts[0][0])} ${r(pts[0][1])}`;\n const segs = closed ? n : n - 1;\n for (let i = 0; i < segs; i++) {\n const p1 = at(i), p2 = at(i + 1);\n if (!smooth) d += `L${r(p2[0])} ${r(p2[1])}`;\n else {\n const p0 = at(i - 1), p3 = at(i + 2);\n d += `C${r(p1[0] + (p2[0] - p0[0]) / 6)} ${r(p1[1] + (p2[1] - p0[1]) / 6)} ${r(p2[0] - (p3[0] - p1[0]) / 6)} ${r(p2[1] - (p3[1] - p1[1]) / 6)} ${r(p2[0])} ${r(p2[1])}`;\n }\n }\n return closed ? d + 'Z' : d;\n}\n\n/** `count` points spaced evenly by arc length along the polyline through `pts`. */\nexport function resample(pts: ReadonlyArray<Pt>, count: number, closed = false): Array<[number, number]> {\n if (!pts.length || count <= 0) return [];\n if (pts.length === 1) return Array.from({ length: count }, () => [pts[0][0], pts[0][1]]);\n const ring = closed ? [...pts, pts[0]] : pts;\n const cum = [0];\n for (let i = 1; i < ring.length; i++) cum.push(cum[i - 1] + Math.hypot(ring[i][0] - ring[i - 1][0], ring[i][1] - ring[i - 1][1]));\n const total = cum[cum.length - 1];\n const out: Array<[number, number]> = [];\n let seg = 1;\n for (let k = 0; k < count; k++) {\n // closed shapes don't repeat the start point; open ones end exactly on the last point\n const frac = count === 1 ? 0 : closed ? k / count : k / (count - 1);\n const target = frac * total;\n while (seg < cum.length - 1 && cum[seg] < target) seg++;\n const span = cum[seg] - cum[seg - 1];\n const t = span === 0 ? 0 : (target - cum[seg - 1]) / span;\n out.push([ring[seg - 1][0] + (ring[seg][0] - ring[seg - 1][0]) * t, ring[seg - 1][1] + (ring[seg][1] - ring[seg - 1][1]) * t]);\n }\n return out;\n}\n\n/** Resample the points stored in a flat numeric bag (p0 p1 \u2026), returning a bag with `to` points. */\nexport function resampleProps(cur: NumericProps, from: number, to: number, closed: boolean): NumericProps {\n const pts: Pt[] = Array.from({ length: from }, (_, i) => [cur[`p${2 * i}`], cur[`p${2 * i + 1}`]] as Pt);\n const out: NumericProps = {};\n resample(pts, to, closed).forEach(([x, y], i) => ((out[`p${2 * i}`] = x), (out[`p${2 * i + 1}`] = y)));\n return out;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Step through an algorithm frame by frame.
3
+ *
4
+ * Pattern: write your algorithm as a generator that `yield`s a SNAPSHOT of its
5
+ * state at each interesting moment. The player runs it to completion up front
6
+ * and keeps every frame, which is what makes "previous step" and a scrubber
7
+ * possible — you can't run a generator backwards, but you can index an array.
8
+ *
9
+ * function* binarySearch(a, target) {
10
+ * let lo = 0, hi = a.length - 1;
11
+ * while (lo <= hi) {
12
+ * const mid = (lo + hi) >> 1;
13
+ * yield { lo, hi, mid }; // ← snapshot
14
+ * ...
15
+ * }
16
+ * }
17
+ *
18
+ * ⚠ Yield COPIES of mutable data (`[...arr]`, `structuredClone(x)`). If you
19
+ * yield the same array you keep mutating, every stored frame points at the
20
+ * same final array and the animation looks frozen.
21
+ */
22
+ export interface PlayerOptions<S> {
23
+ /** ms between frames when playing. Default 700. */
24
+ interval?: number;
25
+ loop?: boolean;
26
+ /** Safety cap for runaway generators. Default 10 000. */
27
+ maxFrames?: number;
28
+ /** Called on every frame change (including the initial one). */
29
+ onFrame?: (frame: S, index: number, player: Player<S>) => void;
30
+ }
31
+ export interface Player<S> {
32
+ readonly frames: readonly S[];
33
+ readonly index: number;
34
+ readonly frame: S;
35
+ readonly playing: boolean;
36
+ readonly atEnd: boolean;
37
+ next(): void;
38
+ prev(): void;
39
+ seek(i: number): void;
40
+ play(): void;
41
+ pause(): void;
42
+ toggle(): void;
43
+ /** Replace the frames (e.g. after the user changes the input). */
44
+ load(source: Iterable<S>): void;
45
+ setInterval(ms: number): void;
46
+ subscribe(fn: (frame: S, index: number, player: Player<S>) => void): () => void;
47
+ destroy(): void;
48
+ }
49
+ export declare function createPlayer<S>(source: Iterable<S>, o?: PlayerOptions<S>): Player<S>;
package/dist/player.js ADDED
@@ -0,0 +1,100 @@
1
+ function createPlayer(source, o = {}) {
2
+ let interval = o.interval ?? 700;
3
+ const max = o.maxFrames ?? 1e4;
4
+ let frames = [];
5
+ let index = 0;
6
+ let timer = null;
7
+ const subs = /* @__PURE__ */ new Set();
8
+ if (o.onFrame) subs.add(o.onFrame);
9
+ const collect = (src) => {
10
+ const out = [];
11
+ for (const f of src) {
12
+ out.push(f);
13
+ if (out.length >= max) {
14
+ console.warn(`[dsviz] player stopped collecting at ${max} frames`);
15
+ break;
16
+ }
17
+ }
18
+ return out;
19
+ };
20
+ const notify = () => {
21
+ if (!frames.length) return;
22
+ for (const fn of subs) fn(frames[index], index, player);
23
+ };
24
+ const tick = () => {
25
+ if (index >= frames.length - 1) {
26
+ if (!o.loop) return pause();
27
+ index = -1;
28
+ }
29
+ index++;
30
+ notify();
31
+ timer = setTimeout(tick, interval);
32
+ };
33
+ function pause() {
34
+ if (timer) clearTimeout(timer);
35
+ timer = null;
36
+ notify();
37
+ }
38
+ const player = {
39
+ get frames() {
40
+ return frames;
41
+ },
42
+ get index() {
43
+ return index;
44
+ },
45
+ get frame() {
46
+ return frames[index];
47
+ },
48
+ get playing() {
49
+ return timer !== null;
50
+ },
51
+ get atEnd() {
52
+ return index >= frames.length - 1;
53
+ },
54
+ next() {
55
+ if (index < frames.length - 1) index++, notify();
56
+ },
57
+ prev() {
58
+ if (index > 0) index--, notify();
59
+ },
60
+ seek(i) {
61
+ const j = Math.max(0, Math.min(frames.length - 1, Math.round(i)));
62
+ if (j !== index) index = j, notify();
63
+ },
64
+ play() {
65
+ if (timer) return;
66
+ if (index >= frames.length - 1) index = -1;
67
+ timer = setTimeout(tick, 0);
68
+ },
69
+ pause,
70
+ toggle() {
71
+ timer ? pause() : player.play();
72
+ },
73
+ load(src) {
74
+ if (timer) clearTimeout(timer);
75
+ timer = null;
76
+ frames = collect(src);
77
+ index = 0;
78
+ notify();
79
+ },
80
+ setInterval(ms) {
81
+ interval = ms;
82
+ },
83
+ subscribe(fn) {
84
+ subs.add(fn);
85
+ return () => subs.delete(fn);
86
+ },
87
+ destroy() {
88
+ if (timer) clearTimeout(timer);
89
+ timer = null;
90
+ subs.clear();
91
+ }
92
+ };
93
+ frames = collect(source);
94
+ queueMicrotask(notify);
95
+ return player;
96
+ }
97
+ export {
98
+ createPlayer
99
+ };
100
+ //# sourceMappingURL=player.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/player.ts"],
4
+ "sourcesContent": ["/**\n * Step through an algorithm frame by frame.\n *\n * Pattern: write your algorithm as a generator that `yield`s a SNAPSHOT of its\n * state at each interesting moment. The player runs it to completion up front\n * and keeps every frame, which is what makes \"previous step\" and a scrubber\n * possible \u2014 you can't run a generator backwards, but you can index an array.\n *\n * function* binarySearch(a, target) {\n * let lo = 0, hi = a.length - 1;\n * while (lo <= hi) {\n * const mid = (lo + hi) >> 1;\n * yield { lo, hi, mid }; // \u2190 snapshot\n * ...\n * }\n * }\n *\n * \u26A0 Yield COPIES of mutable data (`[...arr]`, `structuredClone(x)`). If you\n * yield the same array you keep mutating, every stored frame points at the\n * same final array and the animation looks frozen.\n */\n\nexport interface PlayerOptions<S> {\n /** ms between frames when playing. Default 700. */\n interval?: number;\n loop?: boolean;\n /** Safety cap for runaway generators. Default 10 000. */\n maxFrames?: number;\n /** Called on every frame change (including the initial one). */\n onFrame?: (frame: S, index: number, player: Player<S>) => void;\n}\n\nexport interface Player<S> {\n readonly frames: readonly S[];\n readonly index: number;\n readonly frame: S;\n readonly playing: boolean;\n readonly atEnd: boolean;\n next(): void;\n prev(): void;\n seek(i: number): void;\n play(): void;\n pause(): void;\n toggle(): void;\n /** Replace the frames (e.g. after the user changes the input). */\n load(source: Iterable<S>): void;\n setInterval(ms: number): void;\n subscribe(fn: (frame: S, index: number, player: Player<S>) => void): () => void;\n destroy(): void;\n}\n\nexport function createPlayer<S>(source: Iterable<S>, o: PlayerOptions<S> = {}): Player<S> {\n let interval = o.interval ?? 700;\n const max = o.maxFrames ?? 10_000;\n let frames: S[] = [];\n let index = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n const subs = new Set<(f: S, i: number, p: Player<S>) => void>();\n if (o.onFrame) subs.add(o.onFrame);\n\n const collect = (src: Iterable<S>) => {\n const out: S[] = [];\n for (const f of src) {\n out.push(f);\n if (out.length >= max) {\n console.warn(`[dsviz] player stopped collecting at ${max} frames`);\n break;\n }\n }\n return out;\n };\n\n const notify = () => {\n if (!frames.length) return;\n for (const fn of subs) fn(frames[index], index, player);\n };\n\n const tick = () => {\n if (index >= frames.length - 1) {\n if (!o.loop) return pause();\n index = -1;\n }\n index++;\n notify();\n timer = setTimeout(tick, interval);\n };\n\n function pause() {\n if (timer) clearTimeout(timer);\n timer = null;\n notify(); // so UIs can update their play/pause button\n }\n\n const player: Player<S> = {\n get frames() {\n return frames;\n },\n get index() {\n return index;\n },\n get frame() {\n return frames[index];\n },\n get playing() {\n return timer !== null;\n },\n get atEnd() {\n return index >= frames.length - 1;\n },\n next() {\n if (index < frames.length - 1) (index++, notify());\n },\n prev() {\n if (index > 0) (index--, notify());\n },\n seek(i) {\n const j = Math.max(0, Math.min(frames.length - 1, Math.round(i)));\n if (j !== index) (index = j, notify());\n },\n play() {\n if (timer) return;\n if (index >= frames.length - 1) index = -1; // restart from the top\n timer = setTimeout(tick, 0);\n },\n pause,\n toggle() {\n timer ? pause() : player.play();\n },\n load(src) {\n if (timer) clearTimeout(timer);\n timer = null;\n frames = collect(src);\n index = 0;\n notify();\n },\n setInterval(ms) {\n interval = ms;\n },\n subscribe(fn) {\n subs.add(fn);\n return () => subs.delete(fn);\n },\n destroy() {\n if (timer) clearTimeout(timer);\n timer = null;\n subs.clear();\n },\n };\n\n frames = collect(source);\n queueMicrotask(notify); // let the caller finish wiring up before the first frame\n return player;\n}\n"],
5
+ "mappings": "AAmDO,SAAS,aAAgB,QAAqB,IAAsB,CAAC,GAAc;AACxF,MAAI,WAAW,EAAE,YAAY;AAC7B,QAAM,MAAM,EAAE,aAAa;AAC3B,MAAI,SAAc,CAAC;AACnB,MAAI,QAAQ;AACZ,MAAI,QAA8C;AAClD,QAAM,OAAO,oBAAI,IAA6C;AAC9D,MAAI,EAAE,QAAS,MAAK,IAAI,EAAE,OAAO;AAEjC,QAAM,UAAU,CAAC,QAAqB;AACpC,UAAM,MAAW,CAAC;AAClB,eAAW,KAAK,KAAK;AACnB,UAAI,KAAK,CAAC;AACV,UAAI,IAAI,UAAU,KAAK;AACrB,gBAAQ,KAAK,wCAAwC,GAAG,SAAS;AACjE;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,MAAM;AACnB,QAAI,CAAC,OAAO,OAAQ;AACpB,eAAW,MAAM,KAAM,IAAG,OAAO,KAAK,GAAG,OAAO,MAAM;AAAA,EACxD;AAEA,QAAM,OAAO,MAAM;AACjB,QAAI,SAAS,OAAO,SAAS,GAAG;AAC9B,UAAI,CAAC,EAAE,KAAM,QAAO,MAAM;AAC1B,cAAQ;AAAA,IACV;AACA;AACA,WAAO;AACP,YAAQ,WAAW,MAAM,QAAQ;AAAA,EACnC;AAEA,WAAS,QAAQ;AACf,QAAI,MAAO,cAAa,KAAK;AAC7B,YAAQ;AACR,WAAO;AAAA,EACT;AAEA,QAAM,SAAoB;AAAA,IACxB,IAAI,SAAS;AACX,aAAO;AAAA,IACT;AAAA,IACA,IAAI,QAAQ;AACV,aAAO;AAAA,IACT;AAAA,IACA,IAAI,QAAQ;AACV,aAAO,OAAO,KAAK;AAAA,IACrB;AAAA,IACA,IAAI,UAAU;AACZ,aAAO,UAAU;AAAA,IACnB;AAAA,IACA,IAAI,QAAQ;AACV,aAAO,SAAS,OAAO,SAAS;AAAA,IAClC;AAAA,IACA,OAAO;AACL,UAAI,QAAQ,OAAO,SAAS,EAAG,CAAC,SAAS,OAAO;AAAA,IAClD;AAAA,IACA,OAAO;AACL,UAAI,QAAQ,EAAG,CAAC,SAAS,OAAO;AAAA,IAClC;AAAA,IACA,KAAK,GAAG;AACN,YAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,SAAS,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC;AAChE,UAAI,MAAM,MAAO,CAAC,QAAQ,GAAG,OAAO;AAAA,IACtC;AAAA,IACA,OAAO;AACL,UAAI,MAAO;AACX,UAAI,SAAS,OAAO,SAAS,EAAG,SAAQ;AACxC,cAAQ,WAAW,MAAM,CAAC;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,SAAS;AACP,cAAQ,MAAM,IAAI,OAAO,KAAK;AAAA,IAChC;AAAA,IACA,KAAK,KAAK;AACR,UAAI,MAAO,cAAa,KAAK;AAC7B,cAAQ;AACR,eAAS,QAAQ,GAAG;AACpB,cAAQ;AACR,aAAO;AAAA,IACT;AAAA,IACA,YAAY,IAAI;AACd,iBAAW;AAAA,IACb;AAAA,IACA,UAAU,IAAI;AACZ,WAAK,IAAI,EAAE;AACX,aAAO,MAAM,KAAK,OAAO,EAAE;AAAA,IAC7B;AAAA,IACA,UAAU;AACR,UAAI,MAAO,cAAa,KAAK;AAC7B,cAAQ;AACR,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAEA,WAAS,QAAQ,MAAM;AACvB,iBAAe,MAAM;AACrB,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from './plane';
2
+ export * from './marks';
@@ -0,0 +1,3 @@
1
+ export * from "./plane.js";
2
+ export * from "./marks.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/plot/index.ts"],
4
+ "sourcesContent": ["export * from './plane';\nexport * from './marks';\n"],
5
+ "mappings": "AAAA,cAAc;AACd,cAAc;",
6
+ "names": []
7
+ }
@@ -0,0 +1,120 @@
1
+ import { Scene, Tone } from '../scene';
2
+ import { Vec } from '../vec';
3
+ import { Plane } from './plane';
4
+ type Fn = (x: number) => number;
5
+ export interface AxesOptions {
6
+ id?: string;
7
+ /** Tick positions, or a step. Default: nice ticks. */
8
+ xTicks?: number[] | number;
9
+ yTicks?: number[] | number;
10
+ grid?: boolean;
11
+ /** Tick labels. Default true. */
12
+ labels?: boolean;
13
+ xLabel?: string;
14
+ yLabel?: string;
15
+ /** Hide the 0 label at the origin (it collides with both axes). Default true. */
16
+ hideZero?: boolean;
17
+ }
18
+ /**
19
+ * Axes through the origin (or along the edge when 0 is out of range), with
20
+ * optional grid lines and tick labels. Ticks are keyed by VALUE, so if the
21
+ * plane's range changes the tick for "2" slides to its new place.
22
+ */
23
+ export declare function axes(p: Plane, o?: AxesOptions): Scene;
24
+ export interface FnOptions {
25
+ id?: string;
26
+ tone?: Tone;
27
+ width?: number;
28
+ /** Sample count across the domain. Default 160. Keep it fixed so the curve can morph point-for-point. */
29
+ samples?: number;
30
+ /** Plot only this x-range. Default: the plane's. */
31
+ domain?: [number, number];
32
+ dashed?: boolean;
33
+ }
34
+ /**
35
+ * Sample y = f(x) into path(s), clipped to the plane. The curve is SPLIT
36
+ * wherever f is undefined (NaN / ±Infinity), leaves the visible y-range, or
37
+ * jumps by more than the plane's height between two samples (tan x, 1/x), so
38
+ * you never get a vertical line through an asymptote.
39
+ * Pieces are keyed `id:0`, `id:1`, … so the main piece keeps its identity.
40
+ */
41
+ export declare function fn(p: Plane, f: Fn, o?: FnOptions): Scene;
42
+ export interface PointOptions {
43
+ id: string;
44
+ label?: string;
45
+ tone?: Tone;
46
+ draggable?: boolean;
47
+ r?: number;
48
+ /** Where the label sits relative to the point, in px. Default (8, -10). */
49
+ labelOffset?: Vec;
50
+ data?: unknown;
51
+ /** Pulse a "drag me" ring (turn off once touched). */
52
+ hint?: boolean;
53
+ }
54
+ /** A dot at world (x, y). With `draggable`, it gets a big hit target; drag events carry `data`. */
55
+ export declare function point(p: Plane, x: number, y: number, o: PointOptions): Scene;
56
+ export interface LineOptions {
57
+ id: string;
58
+ tone?: Tone;
59
+ width?: number;
60
+ dashed?: boolean;
61
+ role?: string;
62
+ }
63
+ /** Straight segment between two world points. */
64
+ export declare function segment(p: Plane, a: Vec, b: Vec, o: LineOptions): Scene;
65
+ /** An arrow from `from` to `to` (world coordinates), e.g. a vector. */
66
+ export declare function vector(p: Plane, from: Vec, to: Vec, o: LineOptions & {
67
+ label?: string;
68
+ }): Scene;
69
+ /** Numerical derivative (central difference). Good to ~1e-6 for smooth f. */
70
+ export declare const derivative: (f: Fn, x: number, h?: number) => number;
71
+ /**
72
+ * The tangent line to f at x0, drawn `span` world units either side, clipped
73
+ * to the plane. Returns the scene plus the slope it used.
74
+ */
75
+ /**
76
+ * The part of segment a→b inside the plane's x/y ranges (Liang–Barsky).
77
+ * Write the segment as a + t·(b − a), t ∈ [0, 1]. Each of the four edges of
78
+ * the box cuts off t below or above some value; keep the t-range that survives
79
+ * all four. Returns null if nothing is left.
80
+ */
81
+ export declare function clipToPlane(p: Plane, a: Vec, b: Vec): [Vec, Vec] | null;
82
+ export declare function tangent(p: Plane, f: Fn, x0: number, o?: LineOptions & {
83
+ span?: number;
84
+ }): Scene & {
85
+ slope: number;
86
+ };
87
+ export interface AreaOptions {
88
+ id?: string;
89
+ tone?: Tone;
90
+ samples?: number;
91
+ /** Shade between f and g instead of f and the x-axis. */
92
+ g?: Fn;
93
+ }
94
+ /** Shaded region between f and the x-axis (or g) for a ≤ x ≤ b, as one closed path. */
95
+ export declare function area(p: Plane, f: Fn, a: number, b: number, o?: AreaOptions): Scene;
96
+ export type RiemannRule = 'left' | 'right' | 'mid' | 'trap';
97
+ /**
98
+ * Riemann sum of f over [a, b] with n pieces. Each piece is keyed by its INDEX,
99
+ * so changing n makes existing rectangles resize and new ones fade in, rather
100
+ * than everything being replaced. `trap` draws trapezoids (closed paths).
101
+ * Returns the scene and the numeric sum.
102
+ */
103
+ export declare function riemann(p: Plane, f: Fn, a: number, b: number, n: number, rule?: RiemannRule, o?: {
104
+ id?: string;
105
+ tone?: Tone;
106
+ }): Scene & {
107
+ sum: number;
108
+ };
109
+ /** Numerical integral by Simpson's rule (n even). */
110
+ export declare function integrate(f: Fn, a: number, b: number, n?: number): number;
111
+ /** A maths label at world (x, y): italic serif via role 'math'. */
112
+ export declare function label(p: Plane, x: number, y: number, s: string, o: {
113
+ id: string;
114
+ tone?: Tone;
115
+ size?: number;
116
+ anchor?: 'start' | 'middle' | 'end';
117
+ dx?: number;
118
+ dy?: number;
119
+ }): Scene;
120
+ export {};
@@ -0,0 +1,200 @@
1
+ import { handle } from "../handle.js";
2
+ import { arrow, path, text } from "../scene.js";
3
+ import { V2 } from "../vec.js";
4
+ import { niceTicks } from "./plane.js";
5
+ const fmt = (v) => (Number.isInteger(v) ? String(v) : String(+v.toFixed(2))).replace("-", "\u2212");
6
+ const ticksFor = (spec, lo, hi) => {
7
+ if (Array.isArray(spec)) return spec;
8
+ if (typeof spec === "number") {
9
+ const out = [];
10
+ for (let v = Math.ceil(lo / spec) * spec; v <= hi + 1e-9; v += spec) out.push(+v.toPrecision(12));
11
+ return out;
12
+ }
13
+ return niceTicks(lo, hi);
14
+ };
15
+ function axes(p, o = {}) {
16
+ const id = o.id ?? "axes";
17
+ const xs = ticksFor(o.xTicks, p.x[0], p.x[1]);
18
+ const ys = ticksFor(o.yTicks, p.y[0], p.y[1]);
19
+ const ax = p.sy(Math.min(p.y[1], Math.max(p.y[0], 0)));
20
+ const ay = p.sx(Math.min(p.x[1], Math.max(p.x[0], 0)));
21
+ const nodes = [];
22
+ if (o.grid ?? true) {
23
+ for (const v of xs) nodes.push(path({ id: `${id}:gx:${v}`, points: [[p.sx(v), 0], [p.sx(v), p.height]], role: "grid", width: 1 }));
24
+ for (const v of ys) nodes.push(path({ id: `${id}:gy:${v}`, points: [[0, p.sy(v)], [p.width, p.sy(v)]], role: "grid", width: 1 }));
25
+ }
26
+ nodes.push(
27
+ arrow({ id: `${id}:x`, x1: 0, y1: ax, x2: p.width + 8, y2: ax, role: "axis", width: 1.25 }),
28
+ arrow({ id: `${id}:y`, x1: ay, y1: p.height, x2: ay, y2: -8, role: "axis", width: 1.25 })
29
+ );
30
+ const hideZero = o.hideZero ?? true;
31
+ if (o.labels ?? true) {
32
+ for (const v of xs) {
33
+ if (hideZero && v === 0) continue;
34
+ nodes.push(path({ id: `${id}:tx:${v}`, points: [[p.sx(v), ax - 3], [p.sx(v), ax + 3]], role: "tick", width: 1 }));
35
+ nodes.push(text({ id: `${id}:lx:${v}`, x: p.sx(v), y: ax + 15, text: fmt(v), size: 12, role: "tick-label" }));
36
+ }
37
+ for (const v of ys) {
38
+ if (hideZero && v === 0) continue;
39
+ nodes.push(path({ id: `${id}:ty:${v}`, points: [[ay - 3, p.sy(v)], [ay + 3, p.sy(v)]], role: "tick", width: 1 }));
40
+ nodes.push(text({ id: `${id}:ly:${v}`, x: ay - 8, y: p.sy(v), text: fmt(v), size: 12, role: "tick-label", anchor: "end" }));
41
+ }
42
+ }
43
+ if (o.xLabel) nodes.push(text({ id: `${id}:xl`, x: p.width + 14, y: ax, text: o.xLabel, size: 15, role: "math", anchor: "start" }));
44
+ if (o.yLabel) nodes.push(text({ id: `${id}:yl`, x: ay, y: -20, text: o.yLabel, size: 15, role: "math" }));
45
+ return { nodes };
46
+ }
47
+ function fn(p, f, o = {}) {
48
+ const id = o.id ?? "f";
49
+ const [a, b] = o.domain ?? p.x;
50
+ const n = Math.max(2, o.samples ?? 160);
51
+ const [ylo, yhi] = p.y;
52
+ const span = yhi - ylo;
53
+ const inside = (y) => y >= ylo && y <= yhi;
54
+ const pieces = [];
55
+ let cur = [];
56
+ let prev = null;
57
+ const end = () => {
58
+ if (cur.length > 1) pieces.push(cur);
59
+ cur = [];
60
+ };
61
+ for (let i = 0; i < n; i++) {
62
+ const x = a + (b - a) * i / (n - 1);
63
+ const y = f(x);
64
+ if (!Number.isFinite(y)) {
65
+ end();
66
+ prev = null;
67
+ continue;
68
+ }
69
+ if (prev) {
70
+ if (Math.abs(y - prev.y) > span) {
71
+ end();
72
+ } else if (inside(prev.y) !== inside(y)) {
73
+ const edge = (inside(prev.y) ? y : prev.y) > yhi ? yhi : ylo;
74
+ const t = (edge - prev.y) / (y - prev.y);
75
+ const ex = prev.x + (x - prev.x) * t;
76
+ cur.push([p.sx(ex), p.sy(edge)]);
77
+ if (inside(prev.y)) end();
78
+ }
79
+ }
80
+ if (inside(y)) cur.push([p.sx(x), p.sy(y)]);
81
+ prev = { x, y };
82
+ }
83
+ end();
84
+ return {
85
+ nodes: pieces.map((pts, k) => path({ id: `${id}:${k}`, points: pts, role: "curve", tone: o.tone, width: o.width ?? 2.25, dashed: o.dashed }))
86
+ };
87
+ }
88
+ function point(p, x, y, o) {
89
+ const c = p.toPx(x, y);
90
+ const off = o.labelOffset ?? V2(8, -12);
91
+ const nodes = handle({ id: o.id, x: c.x, y: c.y, r: o.r ?? 5, tone: o.tone, draggable: !!o.draggable, hint: o.hint, data: o.data ?? { id: o.id } });
92
+ if (o.label) nodes.push(text({ id: `${o.id}:label`, x: c.x + off.x, y: c.y + off.y, text: o.label, size: 13, role: "math", tone: o.tone, anchor: off.x < 0 ? "end" : "start" }));
93
+ return { nodes };
94
+ }
95
+ function segment(p, a, b, o) {
96
+ const A = p.toPx(a), B = p.toPx(b);
97
+ return { nodes: [path({ id: o.id, points: [[A.x, A.y], [B.x, B.y]], role: o.role ?? "curve", tone: o.tone, width: o.width ?? 1.75, dashed: o.dashed })] };
98
+ }
99
+ function vector(p, from, to, o) {
100
+ const A = p.toPx(from), B = p.toPx(to);
101
+ const nodes = [arrow({ id: o.id, x1: A.x, y1: A.y, x2: B.x, y2: B.y, tone: o.tone ?? "accent", width: o.width ?? 2.25, dashed: o.dashed })];
102
+ if (o.label) {
103
+ const mx = (A.x + B.x) / 2, my = (A.y + B.y) / 2;
104
+ const dx = B.x - A.x, dy = B.y - A.y, l = Math.hypot(dx, dy) || 1;
105
+ nodes.push(text({ id: `${o.id}:label`, x: mx + dy / l * 14, y: my - dx / l * 14, text: o.label, size: 14, role: "math", tone: o.tone ?? "accent" }));
106
+ }
107
+ return { nodes };
108
+ }
109
+ const derivative = (f, x, h = 1e-4) => (f(x + h) - f(x - h)) / (2 * h);
110
+ function clipToPlane(p, a, b) {
111
+ const dx = b.x - a.x, dy = b.y - a.y;
112
+ let t0 = 0, t1 = 1;
113
+ const edges = [
114
+ [-dx, a.x - p.x[0]],
115
+ [dx, p.x[1] - a.x],
116
+ [-dy, a.y - p.y[0]],
117
+ [dy, p.y[1] - a.y]
118
+ ];
119
+ for (const [q, r] of edges) {
120
+ if (q === 0) {
121
+ if (r < 0) return null;
122
+ continue;
123
+ }
124
+ const t = r / q;
125
+ if (q < 0) t0 = Math.max(t0, t);
126
+ else t1 = Math.min(t1, t);
127
+ if (t0 > t1) return null;
128
+ }
129
+ return [V2(a.x + dx * t0, a.y + dy * t0), V2(a.x + dx * t1, a.y + dy * t1)];
130
+ }
131
+ function tangent(p, f, x0, o = { id: "tangent" }) {
132
+ const m = derivative(f, x0);
133
+ const y0 = f(x0);
134
+ const span = o.span ?? (p.x[1] - p.x[0]) / 4;
135
+ const maxDx = Math.abs(m) > 1e-9 ? (p.y[1] - p.y[0]) / Math.abs(m) / 1.5 : span;
136
+ const d = Math.min(span, maxDx);
137
+ const [s0, s1] = clipToPlane(p, V2(x0 - d, y0 - m * d), V2(x0 + d, y0 + m * d)) ?? [V2(x0, y0), V2(x0, y0)];
138
+ return { ...segment(p, s0, s1, { ...o, tone: o.tone ?? "active" }), slope: m };
139
+ }
140
+ function area(p, f, a, b, o = {}) {
141
+ const n = o.samples ?? 80;
142
+ const lo = Math.min(a, b), hi = Math.max(a, b);
143
+ const top = [], bottom = [];
144
+ const g = o.g ?? (() => 0);
145
+ for (let i = 0; i < n; i++) {
146
+ const x = lo + (hi - lo) * i / (n - 1);
147
+ top.push([p.sx(x), p.sy(clampY(p, f(x)))]);
148
+ bottom.push([p.sx(x), p.sy(clampY(p, g(x)))]);
149
+ }
150
+ return { nodes: [path({ id: o.id ?? "area", points: [...top, ...bottom.reverse()], closed: true, role: "area", tone: o.tone })] };
151
+ }
152
+ const clampY = (p, y) => Number.isFinite(y) ? Math.min(p.y[1], Math.max(p.y[0], y)) : 0;
153
+ function riemann(p, f, a, b, n, rule = "left", o = {}) {
154
+ const id = o.id ?? "riemann";
155
+ const w = (b - a) / n;
156
+ const nodes = [];
157
+ let sum = 0;
158
+ for (let i = 0; i < n; i++) {
159
+ const xl = a + i * w, xr = xl + w;
160
+ let pts;
161
+ if (rule === "trap") {
162
+ const yl = f(xl), yr = f(xr);
163
+ sum += (yl + yr) / 2 * w;
164
+ pts = [[xl, 0], [xl, yl], [xr, yr], [xr, 0]];
165
+ } else {
166
+ const h = f(rule === "left" ? xl : rule === "right" ? xr : (xl + xr) / 2);
167
+ sum += h * w;
168
+ pts = [[xl, 0], [xl, h], [xr, h], [xr, 0]];
169
+ }
170
+ const px = pts.map(([x, y]) => [p.sx(x), p.sy(clampY(p, y))]);
171
+ const neg = rule === "trap" ? f(xl) + f(xr) < 0 : px[1][1] > px[0][1];
172
+ nodes.push(path({ id: `${id}:${i}`, points: px, closed: true, role: "bar", tone: o.tone ?? (neg ? "danger" : "accent"), width: 1 }));
173
+ }
174
+ return { nodes, sum };
175
+ }
176
+ function integrate(f, a, b, n = 400) {
177
+ const h = (b - a) / n;
178
+ let s = f(a) + f(b);
179
+ for (let i = 1; i < n; i++) s += f(a + i * h) * (i % 2 ? 4 : 2);
180
+ return s * h / 3;
181
+ }
182
+ function label(p, x, y, s, o) {
183
+ const c = p.toPx(x, y);
184
+ return { nodes: [text({ id: o.id, x: c.x + (o.dx ?? 0), y: c.y + (o.dy ?? 0), text: s, size: o.size ?? 14, role: "math", tone: o.tone, anchor: o.anchor })] };
185
+ }
186
+ export {
187
+ area,
188
+ axes,
189
+ clipToPlane,
190
+ derivative,
191
+ fn,
192
+ integrate,
193
+ label,
194
+ point,
195
+ riemann,
196
+ segment,
197
+ tangent,
198
+ vector
199
+ };
200
+ //# sourceMappingURL=marks.js.map