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.
- package/LICENSE +21 -0
- package/README.md +35 -2
- package/dist/camera.d.ts +25 -0
- package/dist/camera.js +27 -0
- package/dist/camera.js.map +7 -0
- package/dist/connector.d.ts +65 -0
- package/dist/connector.js +108 -0
- package/dist/connector.js.map +7 -0
- package/dist/describe.d.ts +44 -0
- package/dist/describe.js +131 -0
- package/dist/describe.js.map +7 -0
- package/dist/ds/array.d.ts +51 -0
- package/dist/ds/array.js +62 -0
- package/dist/ds/array.js.map +7 -0
- package/dist/ds/common.d.ts +9 -0
- package/dist/ds/common.js +10 -0
- package/dist/ds/common.js.map +7 -0
- package/dist/ds/graph.d.ts +39 -0
- package/dist/ds/graph.js +57 -0
- package/dist/ds/graph.js.map +7 -0
- package/dist/ds/grid.d.ts +30 -0
- package/dist/ds/grid.js +41 -0
- package/dist/ds/grid.js.map +7 -0
- package/dist/ds/index.d.ts +8 -0
- package/dist/ds/index.js +9 -0
- package/dist/ds/index.js.map +7 -0
- package/dist/ds/linkedList.d.ts +46 -0
- package/dist/ds/linkedList.js +68 -0
- package/dist/ds/linkedList.js.map +7 -0
- package/dist/ds/pointer.d.ts +23 -0
- package/dist/ds/pointer.js +35 -0
- package/dist/ds/pointer.js.map +7 -0
- package/dist/ds/stackQueue.d.ts +5 -0
- package/dist/ds/stackQueue.js +49 -0
- package/dist/ds/stackQueue.js.map +7 -0
- package/dist/ds/tree.d.ts +53 -0
- package/dist/ds/tree.js +87 -0
- package/dist/ds/tree.js.map +7 -0
- package/dist/easing.d.ts +14 -0
- package/dist/easing.js +21 -0
- package/dist/easing.js.map +7 -0
- package/dist/fx.d.ts +21 -0
- package/dist/fx.js +27 -0
- package/dist/fx.js.map +7 -0
- package/dist/geom/index.d.ts +2 -0
- package/dist/geom/index.js +3 -0
- package/dist/geom/index.js.map +7 -0
- package/dist/geom/math.d.ts +34 -0
- package/dist/geom/math.js +74 -0
- package/dist/geom/math.js.map +7 -0
- package/dist/geom/shapes.d.ts +69 -0
- package/dist/geom/shapes.js +105 -0
- package/dist/geom/shapes.js.map +7 -0
- package/dist/handle.d.ts +30 -0
- package/dist/handle.js +13 -0
- package/dist/handle.js.map +7 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +7 -0
- package/dist/mathtext.d.ts +29 -0
- package/dist/mathtext.js +142 -0
- package/dist/mathtext.js.map +7 -0
- package/dist/mem/index.d.ts +2 -0
- package/dist/mem/index.js +3 -0
- package/dist/mem/index.js.map +7 -0
- package/dist/mem/memory.d.ts +47 -0
- package/dist/mem/memory.js +41 -0
- package/dist/mem/memory.js.map +7 -0
- package/dist/mem/stackHeap.d.ts +48 -0
- package/dist/mem/stackHeap.js +76 -0
- package/dist/mem/stackHeap.js.map +7 -0
- package/dist/path.d.ts +23 -0
- package/dist/path.js +49 -0
- package/dist/path.js.map +7 -0
- package/dist/player.d.ts +49 -0
- package/dist/player.js +100 -0
- package/dist/player.js.map +7 -0
- package/dist/plot/index.d.ts +2 -0
- package/dist/plot/index.js +3 -0
- package/dist/plot/index.js.map +7 -0
- package/dist/plot/marks.d.ts +120 -0
- package/dist/plot/marks.js +200 -0
- package/dist/plot/marks.js.map +7 -0
- package/dist/plot/plane.d.ts +45 -0
- package/dist/plot/plane.js +41 -0
- package/dist/plot/plane.js.map +7 -0
- package/dist/prob/bars.d.ts +67 -0
- package/dist/prob/bars.js +82 -0
- package/dist/prob/bars.js.map +7 -0
- package/dist/prob/index.d.ts +3 -0
- package/dist/prob/index.js +4 -0
- package/dist/prob/index.js.map +7 -0
- package/dist/prob/tree.d.ts +39 -0
- package/dist/prob/tree.js +57 -0
- package/dist/prob/tree.js.map +7 -0
- package/dist/prob/venn.d.ts +38 -0
- package/dist/prob/venn.js +89 -0
- package/dist/prob/venn.js.map +7 -0
- package/dist/react/Diagram.d.ts +47 -0
- package/dist/react/Diagram.js +41 -0
- package/dist/react/Diagram.js.map +7 -0
- package/dist/react/MathText.d.ts +9 -0
- package/dist/react/MathText.js +15 -0
- package/dist/react/MathText.js.map +7 -0
- package/dist/react/controls-styles.d.ts +9 -0
- package/dist/react/controls-styles.js +109 -0
- package/dist/react/controls-styles.js.map +7 -0
- package/dist/react/controls.d.ts +84 -0
- package/dist/react/controls.js +146 -0
- package/dist/react/controls.js.map +7 -0
- package/dist/react/hooks.d.ts +48 -0
- package/dist/react/hooks.js +67 -0
- package/dist/react/hooks.js.map +7 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +5 -0
- package/dist/react/index.js.map +7 -0
- package/dist/render.d.ts +61 -0
- package/dist/render.js +294 -0
- package/dist/render.js.map +7 -0
- package/dist/scene.d.ts +138 -0
- package/dist/scene.js +94 -0
- package/dist/scene.js.map +7 -0
- package/dist/sched/gantt.d.ts +49 -0
- package/dist/sched/gantt.js +65 -0
- package/dist/sched/gantt.js.map +7 -0
- package/dist/sched/index.d.ts +2 -0
- package/dist/sched/index.js +3 -0
- package/dist/sched/index.js.map +7 -0
- package/dist/sched/simulate.d.ts +55 -0
- package/dist/sched/simulate.js +78 -0
- package/dist/sched/simulate.js.map +7 -0
- package/dist/static.d.ts +8 -0
- package/dist/static.js +26 -0
- package/dist/static.js.map +7 -0
- package/dist/styles.d.ts +3 -0
- package/dist/styles.js +241 -0
- package/dist/styles.js.map +7 -0
- package/dist/sys/architecture.d.ts +104 -0
- package/dist/sys/architecture.js +167 -0
- package/dist/sys/architecture.js.map +7 -0
- package/dist/sys/index.d.ts +1 -0
- package/dist/sys/index.js +2 -0
- package/dist/sys/index.js.map +7 -0
- package/dist/themes.d.ts +13 -0
- package/dist/themes.js +8 -0
- package/dist/themes.js.map +7 -0
- package/dist/uml/classDiagram.d.ts +72 -0
- package/dist/uml/classDiagram.js +84 -0
- package/dist/uml/classDiagram.js.map +7 -0
- package/dist/uml/index.d.ts +3 -0
- package/dist/uml/index.js +4 -0
- package/dist/uml/index.js.map +7 -0
- package/dist/uml/sequence.d.ts +58 -0
- package/dist/uml/sequence.js +121 -0
- package/dist/uml/sequence.js.map +7 -0
- package/dist/uml/stateMachine.d.ts +40 -0
- package/dist/uml/stateMachine.js +89 -0
- package/dist/uml/stateMachine.js.map +7 -0
- package/dist/vec.d.ts +21 -0
- package/dist/vec.js +24 -0
- package/dist/vec.js.map +7 -0
- package/package.json +93 -4
- package/src/camera.ts +50 -0
- package/src/connector.ts +182 -0
- package/src/describe.ts +181 -0
- package/src/ds/array.ts +108 -0
- package/src/ds/common.ts +16 -0
- package/src/ds/graph.ts +104 -0
- package/src/ds/grid.ts +70 -0
- package/src/ds/index.ts +8 -0
- package/src/ds/linkedList.ts +117 -0
- package/src/ds/pointer.ts +55 -0
- package/src/ds/stackQueue.ts +59 -0
- package/src/ds/tree.ts +139 -0
- package/src/easing.ts +24 -0
- package/src/fx.ts +46 -0
- package/src/geom/index.ts +2 -0
- package/src/geom/math.ts +85 -0
- package/src/geom/shapes.ts +158 -0
- package/src/handle.ts +40 -0
- package/src/index.ts +37 -0
- package/src/mathtext.ts +139 -0
- package/src/mem/index.ts +2 -0
- package/src/mem/memory.ts +85 -0
- package/src/mem/stackHeap.ts +127 -0
- package/src/path.ts +68 -0
- package/src/player.ts +153 -0
- package/src/plot/index.ts +2 -0
- package/src/plot/marks.ts +298 -0
- package/src/plot/plane.ts +82 -0
- package/src/prob/bars.ts +132 -0
- package/src/prob/index.ts +3 -0
- package/src/prob/tree.ts +94 -0
- package/src/prob/venn.ts +144 -0
- package/src/react/Diagram.tsx +97 -0
- package/src/react/MathText.tsx +21 -0
- package/src/react/controls-styles.ts +111 -0
- package/src/react/controls.tsx +297 -0
- package/src/react/hooks.ts +112 -0
- package/src/react/index.ts +4 -0
- package/src/render.ts +445 -0
- package/src/scene.ts +238 -0
- package/src/sched/gantt.ts +110 -0
- package/src/sched/index.ts +2 -0
- package/src/sched/simulate.ts +140 -0
- package/src/static.ts +35 -0
- package/src/styles.ts +272 -0
- package/src/sys/architecture.ts +257 -0
- package/src/sys/index.ts +1 -0
- package/src/themes.ts +26 -0
- package/src/uml/classDiagram.ts +145 -0
- package/src/uml/index.ts +3 -0
- package/src/uml/sequence.ts +218 -0
- package/src/uml/stateMachine.ts +133 -0
- package/src/vec.ts +28 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/ds/stackQueue.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Stacks and queues are just arrays with opinions about where things come\n * from and go to. Both are thin wrappers around array() that set\n * enter/exit offsets, so pushes drop in and pops/dequeues slide away.\n *\n * Remember to pass stable `keys` \u2014 otherwise popping the top looks like\n * \"the last cell faded out\", which happens to be right for a stack but is\n * wrong for a queue (dequeue removes index 0, and every key shifts).\n */\nimport { V2 } from '../vec';\nimport { ArrayDiagram, ArrayOptions, array } from './array';\nimport { resolveTone } from './common';\n\n/** Bottom of the stack is index 0, drawn at the bottom. */\nexport function stack<T>(values: readonly T[], o: ArrayOptions<T> = {}): ArrayDiagram {\n const cell = o.cell ?? 44;\n const gap = o.gap ?? 4;\n // Draw top-to-bottom reversed so index 0 is at the bottom; y is the stack's BOTTOM edge.\n const n = values.length;\n const reversed = [...values].reverse();\n const keys = (o.keys ?? values.map((_, i) => i)).slice().reverse();\n const bottomY = o.y ?? 0;\n const d = array(reversed, {\n showIndices: false,\n ...o,\n id: o.id ?? 'stack',\n cell,\n gap,\n keys,\n vertical: true,\n y: bottomY - n * (cell + gap) + gap,\n tones: o.tones ? (i, v) => resolveTone(o.tones, n - 1 - i, v) : undefined,\n enterOffset: V2(0, -cell * 1.5),\n exitOffset: V2(0, -cell * 1.5),\n });\n // Re-index helpers so stack.center(0) is the bottom element.\n const flip = (i: number) => n - 1 - i;\n return {\n ...d,\n center: (i) => d.center(flip(i)),\n top: (i) => d.top(flip(i)),\n bottom: (i) => d.bottom(flip(i)),\n left: (i) => d.left(flip(i)),\n right: (i) => d.right(flip(i)),\n };\n}\n\n/** Front of the queue is index 0, on the left. Enqueue enters right, dequeue exits left. */\nexport function queue<T>(values: readonly T[], o: ArrayOptions<T> = {}): ArrayDiagram {\n const cell = o.cell ?? 44;\n return array(values, {\n showIndices: false,\n ...o,\n id: o.id ?? 'queue',\n cell,\n enterOffset: V2(cell * 1.5, 0),\n exitOffset: V2(-cell * 1.5, 0),\n });\n}\n"],
|
|
5
|
+
"mappings": "AASA,SAAS,UAAU;AACnB,SAAqC,aAAa;AAClD,SAAS,mBAAmB;AAGrB,SAAS,MAAS,QAAsB,IAAqB,CAAC,GAAiB;AACpF,QAAM,OAAO,EAAE,QAAQ;AACvB,QAAM,MAAM,EAAE,OAAO;AAErB,QAAM,IAAI,OAAO;AACjB,QAAM,WAAW,CAAC,GAAG,MAAM,EAAE,QAAQ;AACrC,QAAM,QAAQ,EAAE,QAAQ,OAAO,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,EAAE,QAAQ;AACjE,QAAM,UAAU,EAAE,KAAK;AACvB,QAAM,IAAI,MAAM,UAAU;AAAA,IACxB,aAAa;AAAA,IACb,GAAG;AAAA,IACH,IAAI,EAAE,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,GAAG,UAAU,KAAK,OAAO,OAAO;AAAA,IAChC,OAAO,EAAE,QAAQ,CAAC,GAAG,MAAM,YAAY,EAAE,OAAO,IAAI,IAAI,GAAG,CAAC,IAAI;AAAA,IAChE,aAAa,GAAG,GAAG,CAAC,OAAO,GAAG;AAAA,IAC9B,YAAY,GAAG,GAAG,CAAC,OAAO,GAAG;AAAA,EAC/B,CAAC;AAED,QAAM,OAAO,CAAC,MAAc,IAAI,IAAI;AACpC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,KAAK,CAAC,CAAC;AAAA,IAC/B,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,CAAC;AAAA,IACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,KAAK,CAAC,CAAC;AAAA,IAC/B,MAAM,CAAC,MAAM,EAAE,KAAK,KAAK,CAAC,CAAC;AAAA,IAC3B,OAAO,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,CAAC;AAAA,EAC/B;AACF;AAGO,SAAS,MAAS,QAAsB,IAAqB,CAAC,GAAiB;AACpF,QAAM,OAAO,EAAE,QAAQ;AACvB,SAAO,MAAM,QAAQ;AAAA,IACnB,aAAa;AAAA,IACb,GAAG;AAAA,IACH,IAAI,EAAE,MAAM;AAAA,IACZ;AAAA,IACA,aAAa,GAAG,OAAO,KAAK,CAAC;AAAA,IAC7B,YAAY,GAAG,CAAC,OAAO,KAAK,CAAC;AAAA,EAC/B,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary trees.
|
|
3
|
+
*
|
|
4
|
+
* Layout algorithm (the simplest one that never overlaps):
|
|
5
|
+
* x = the node's position in an IN-ORDER traversal
|
|
6
|
+
* y = its depth
|
|
7
|
+
* For a BST this has a lovely property: nodes are sorted left→right by key,
|
|
8
|
+
* exactly like the array you'd get from an in-order walk. It's wider than a
|
|
9
|
+
* "tidy" (Reingold–Tilford) layout — implementing that is a great exercise.
|
|
10
|
+
*
|
|
11
|
+
* Ids: a node's circle/label use its key; an edge uses its CHILD's key (every
|
|
12
|
+
* node has exactly one parent edge), so after a rotation the edge re-attaches.
|
|
13
|
+
* New nodes grow out of their parent via `enter`.
|
|
14
|
+
*/
|
|
15
|
+
import { Scene } from '../scene';
|
|
16
|
+
import { Vec } from '../vec';
|
|
17
|
+
import { Key, ToneSpec } from './common';
|
|
18
|
+
export interface BinaryTreeNode {
|
|
19
|
+
key: Key;
|
|
20
|
+
label?: string | number;
|
|
21
|
+
left?: BinaryTreeNode | null;
|
|
22
|
+
right?: BinaryTreeNode | null;
|
|
23
|
+
}
|
|
24
|
+
export interface TreeOptions {
|
|
25
|
+
id?: string;
|
|
26
|
+
/** Top-centre of the tree. */
|
|
27
|
+
x?: number;
|
|
28
|
+
y?: number;
|
|
29
|
+
r?: number;
|
|
30
|
+
/** Horizontal spacing between in-order neighbours. Default 46. */
|
|
31
|
+
dx?: number;
|
|
32
|
+
/** Vertical spacing between levels. Default 66. */
|
|
33
|
+
dy?: number;
|
|
34
|
+
tones?: ToneSpec<Key, BinaryTreeNode>;
|
|
35
|
+
/** Tone for the edge INTO a node (keyed by child key). */
|
|
36
|
+
edgeTones?: ToneSpec<Key, BinaryTreeNode>;
|
|
37
|
+
interactive?: boolean;
|
|
38
|
+
/** Show small "null" stubs for missing children. Default false. */
|
|
39
|
+
showNulls?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface TreeDiagram extends Scene {
|
|
42
|
+
pos(key: Key): Vec | undefined;
|
|
43
|
+
top(key: Key): Vec;
|
|
44
|
+
bottom(key: Key): Vec;
|
|
45
|
+
left(key: Key): Vec;
|
|
46
|
+
right(key: Key): Vec;
|
|
47
|
+
}
|
|
48
|
+
export declare function binaryTree(root: BinaryTreeNode | null | undefined, o?: TreeOptions): TreeDiagram;
|
|
49
|
+
/**
|
|
50
|
+
* Build a tree from LeetCode-style level order: [3, 9, 20, null, null, 15, 7].
|
|
51
|
+
* Keys are the array indices, so the same input always gets the same ids.
|
|
52
|
+
*/
|
|
53
|
+
export declare function treeFromLevelOrder(values: readonly (string | number | null)[]): BinaryTreeNode | null;
|
package/dist/ds/tree.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { arrow, circle, circleEdge, text } from "../scene.js";
|
|
2
|
+
import { V2 } from "../vec.js";
|
|
3
|
+
import { resolveTone, show } from "./common.js";
|
|
4
|
+
function binaryTree(root, o = {}) {
|
|
5
|
+
const id = o.id ?? "tree";
|
|
6
|
+
const r = o.r ?? 20, dx = o.dx ?? 46, dy = o.dy ?? 66;
|
|
7
|
+
const x0 = o.x ?? 0, y0 = (o.y ?? 0) + r;
|
|
8
|
+
const placed = [];
|
|
9
|
+
let counter = 0;
|
|
10
|
+
const walk = (n, parent, depth) => {
|
|
11
|
+
if (!n) return;
|
|
12
|
+
walk(n.left, n, depth + 1);
|
|
13
|
+
placed.push({ node: n, parent, order: counter++, depth });
|
|
14
|
+
walk(n.right, n, depth + 1);
|
|
15
|
+
};
|
|
16
|
+
walk(root, null, 0);
|
|
17
|
+
const pos = /* @__PURE__ */ new Map();
|
|
18
|
+
for (const p of placed) pos.set(String(p.node.key), V2(x0 + (p.order - (counter - 1) / 2) * dx, y0 + p.depth * dy));
|
|
19
|
+
const edges = [];
|
|
20
|
+
const circles = [];
|
|
21
|
+
const labels = [];
|
|
22
|
+
for (const { node, parent } of placed) {
|
|
23
|
+
const p = pos.get(String(node.key));
|
|
24
|
+
const from = parent ? pos.get(String(parent.key)) : void 0;
|
|
25
|
+
const enter = from ? { x: from.x, y: from.y } : void 0;
|
|
26
|
+
const tone = resolveTone(o.tones, node.key, node);
|
|
27
|
+
circles.push(circle({ id: `${id}:node:${node.key}`, x: p.x, y: p.y, r, tone, enter, interactive: o.interactive, data: node }));
|
|
28
|
+
circles.push(text({ id: `${id}:label:${node.key}`, x: p.x, y: p.y, text: show(node.label ?? node.key), size: 15, weight: 600, role: "value", tone, enter }));
|
|
29
|
+
if (from) {
|
|
30
|
+
const a = circleEdge(from, p, r), b = circleEdge(p, from, r);
|
|
31
|
+
edges.push(
|
|
32
|
+
arrow({
|
|
33
|
+
id: `${id}:edge:${node.key}`,
|
|
34
|
+
x1: a.x,
|
|
35
|
+
y1: a.y,
|
|
36
|
+
x2: b.x,
|
|
37
|
+
y2: b.y,
|
|
38
|
+
head: false,
|
|
39
|
+
tone: resolveTone(o.edgeTones, node.key, node),
|
|
40
|
+
enter: { x1: a.x, y1: a.y, x2: a.x, y2: a.y }
|
|
41
|
+
// grows out of the parent
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (o.showNulls) {
|
|
46
|
+
for (const side of ["left", "right"]) {
|
|
47
|
+
if (node[side]) continue;
|
|
48
|
+
const q = V2(p.x + (side === "left" ? -1 : 1) * dx * 0.42, p.y + dy * 0.62);
|
|
49
|
+
const a = circleEdge(p, q, r);
|
|
50
|
+
edges.push(arrow({ id: `${id}:nulledge:${node.key}:${side}`, x1: a.x, y1: a.y, x2: q.x, y2: q.y - 6, head: false, tone: "muted", dashed: true }));
|
|
51
|
+
labels.push(text({ id: `${id}:null:${node.key}:${side}`, x: q.x, y: q.y + 4, text: "\u2205", size: 12, role: "null" }));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const get = (k) => pos.get(String(k)) ?? V2(x0, y0);
|
|
56
|
+
return {
|
|
57
|
+
nodes: [...edges, ...circles, ...labels],
|
|
58
|
+
pos: (k) => pos.get(String(k)),
|
|
59
|
+
top: (k) => V2(get(k).x, get(k).y - r),
|
|
60
|
+
bottom: (k) => V2(get(k).x, get(k).y + r),
|
|
61
|
+
left: (k) => V2(get(k).x - r, get(k).y),
|
|
62
|
+
right: (k) => V2(get(k).x + r, get(k).y)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function treeFromLevelOrder(values) {
|
|
66
|
+
if (!values.length || values[0] == null) return null;
|
|
67
|
+
const mk = (i2) => ({ key: i2, label: values[i2], left: null, right: null });
|
|
68
|
+
const root = mk(0);
|
|
69
|
+
const queue = [root];
|
|
70
|
+
let i = 1;
|
|
71
|
+
while (queue.length && i < values.length) {
|
|
72
|
+
const n = queue.shift();
|
|
73
|
+
for (const side of ["left", "right"]) {
|
|
74
|
+
if (i < values.length && values[i] != null) {
|
|
75
|
+
n[side] = mk(i);
|
|
76
|
+
queue.push(n[side]);
|
|
77
|
+
}
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return root;
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
binaryTree,
|
|
85
|
+
treeFromLevelOrder
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=tree.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/ds/tree.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Binary trees.\n *\n * Layout algorithm (the simplest one that never overlaps):\n * x = the node's position in an IN-ORDER traversal\n * y = its depth\n * For a BST this has a lovely property: nodes are sorted left\u2192right by key,\n * exactly like the array you'd get from an in-order walk. It's wider than a\n * \"tidy\" (Reingold\u2013Tilford) layout \u2014 implementing that is a great exercise.\n *\n * Ids: a node's circle/label use its key; an edge uses its CHILD's key (every\n * node has exactly one parent edge), so after a rotation the edge re-attaches.\n * New nodes grow out of their parent via `enter`.\n */\nimport { Scene, SceneNode, arrow, circle, circleEdge, text } from '../scene';\nimport { V2, Vec } from '../vec';\nimport { Key, ToneSpec, resolveTone, show } from './common';\n\nexport interface BinaryTreeNode {\n key: Key;\n label?: string | number;\n left?: BinaryTreeNode | null;\n right?: BinaryTreeNode | null;\n}\n\nexport interface TreeOptions {\n id?: string;\n /** Top-centre of the tree. */\n x?: number;\n y?: number;\n r?: number;\n /** Horizontal spacing between in-order neighbours. Default 46. */\n dx?: number;\n /** Vertical spacing between levels. Default 66. */\n dy?: number;\n tones?: ToneSpec<Key, BinaryTreeNode>;\n /** Tone for the edge INTO a node (keyed by child key). */\n edgeTones?: ToneSpec<Key, BinaryTreeNode>;\n interactive?: boolean;\n /** Show small \"null\" stubs for missing children. Default false. */\n showNulls?: boolean;\n}\n\nexport interface TreeDiagram extends Scene {\n pos(key: Key): Vec | undefined;\n top(key: Key): Vec;\n bottom(key: Key): Vec;\n left(key: Key): Vec;\n right(key: Key): Vec;\n}\n\nexport function binaryTree(root: BinaryTreeNode | null | undefined, o: TreeOptions = {}): TreeDiagram {\n const id = o.id ?? 'tree';\n const r = o.r ?? 20, dx = o.dx ?? 46, dy = o.dy ?? 66;\n const x0 = o.x ?? 0, y0 = (o.y ?? 0) + r;\n\n // 1. in-order index + depth for every node\n const placed: { node: BinaryTreeNode; parent: BinaryTreeNode | null; order: number; depth: number }[] = [];\n let counter = 0;\n const walk = (n: BinaryTreeNode | null | undefined, parent: BinaryTreeNode | null, depth: number) => {\n if (!n) return;\n walk(n.left, n, depth + 1);\n placed.push({ node: n, parent, order: counter++, depth });\n walk(n.right, n, depth + 1);\n };\n walk(root, null, 0);\n\n // 2. centre the whole thing horizontally on x0\n const pos = new Map<string, Vec>();\n for (const p of placed) pos.set(String(p.node.key), V2(x0 + (p.order - (counter - 1) / 2) * dx, y0 + p.depth * dy));\n\n // 3. emit nodes: edges first so they sit underneath the circles; each label\n // right after its circle, so CSS can move them together\n const edges: SceneNode[] = [];\n const circles: SceneNode[] = [];\n const labels: SceneNode[] = [];\n for (const { node, parent } of placed) {\n const p = pos.get(String(node.key))!;\n const from = parent ? pos.get(String(parent.key))! : undefined;\n const enter = from ? { x: from.x, y: from.y } : undefined;\n const tone = resolveTone(o.tones, node.key, node);\n circles.push(circle({ id: `${id}:node:${node.key}`, x: p.x, y: p.y, r, tone, enter, interactive: o.interactive, data: node }));\n circles.push(text({ id: `${id}:label:${node.key}`, x: p.x, y: p.y, text: show(node.label ?? node.key), size: 15, weight: 600, role: 'value', tone, enter }));\n if (from) {\n const a = circleEdge(from, p, r), b = circleEdge(p, from, r);\n edges.push(\n arrow({\n id: `${id}:edge:${node.key}`,\n x1: a.x, y1: a.y, x2: b.x, y2: b.y,\n head: false,\n tone: resolveTone(o.edgeTones, node.key, node),\n enter: { x1: a.x, y1: a.y, x2: a.x, y2: a.y }, // grows out of the parent\n }),\n );\n }\n if (o.showNulls) {\n for (const side of ['left', 'right'] as const) {\n if (node[side]) continue;\n const q = V2(p.x + (side === 'left' ? -1 : 1) * dx * 0.42, p.y + dy * 0.62);\n const a = circleEdge(p, q, r);\n edges.push(arrow({ id: `${id}:nulledge:${node.key}:${side}`, x1: a.x, y1: a.y, x2: q.x, y2: q.y - 6, head: false, tone: 'muted', dashed: true }));\n labels.push(text({ id: `${id}:null:${node.key}:${side}`, x: q.x, y: q.y + 4, text: '\u2205', size: 12, role: 'null' }));\n }\n }\n }\n\n const get = (k: Key) => pos.get(String(k)) ?? V2(x0, y0);\n return {\n nodes: [...edges, ...circles, ...labels],\n pos: (k) => pos.get(String(k)),\n top: (k) => V2(get(k).x, get(k).y - r),\n bottom: (k) => V2(get(k).x, get(k).y + r),\n left: (k) => V2(get(k).x - r, get(k).y),\n right: (k) => V2(get(k).x + r, get(k).y),\n };\n}\n\n/**\n * Build a tree from LeetCode-style level order: [3, 9, 20, null, null, 15, 7].\n * Keys are the array indices, so the same input always gets the same ids.\n */\nexport function treeFromLevelOrder(values: readonly (string | number | null)[]): BinaryTreeNode | null {\n if (!values.length || values[0] == null) return null;\n const mk = (i: number): BinaryTreeNode => ({ key: i, label: values[i] as string | number, left: null, right: null });\n const root = mk(0);\n const queue = [root];\n let i = 1;\n while (queue.length && i < values.length) {\n const n = queue.shift()!;\n for (const side of ['left', 'right'] as const) {\n if (i < values.length && values[i] != null) {\n n[side] = mk(i);\n queue.push(n[side]!);\n }\n i++;\n }\n }\n return root;\n}\n"],
|
|
5
|
+
"mappings": "AAcA,SAA2B,OAAO,QAAQ,YAAY,YAAY;AAClE,SAAS,UAAe;AACxB,SAAwB,aAAa,YAAY;AAmC1C,SAAS,WAAW,MAAyC,IAAiB,CAAC,GAAgB;AACpG,QAAM,KAAK,EAAE,MAAM;AACnB,QAAM,IAAI,EAAE,KAAK,IAAI,KAAK,EAAE,MAAM,IAAI,KAAK,EAAE,MAAM;AACnD,QAAM,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,KAAK;AAGvC,QAAM,SAAkG,CAAC;AACzG,MAAI,UAAU;AACd,QAAM,OAAO,CAAC,GAAsC,QAA+B,UAAkB;AACnG,QAAI,CAAC,EAAG;AACR,SAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;AACzB,WAAO,KAAK,EAAE,MAAM,GAAG,QAAQ,OAAO,WAAW,MAAM,CAAC;AACxD,SAAK,EAAE,OAAO,GAAG,QAAQ,CAAC;AAAA,EAC5B;AACA,OAAK,MAAM,MAAM,CAAC;AAGlB,QAAM,MAAM,oBAAI,IAAiB;AACjC,aAAW,KAAK,OAAQ,KAAI,IAAI,OAAO,EAAE,KAAK,GAAG,GAAG,GAAG,MAAM,EAAE,SAAS,UAAU,KAAK,KAAK,IAAI,KAAK,EAAE,QAAQ,EAAE,CAAC;AAIlH,QAAM,QAAqB,CAAC;AAC5B,QAAM,UAAuB,CAAC;AAC9B,QAAM,SAAsB,CAAC;AAC7B,aAAW,EAAE,MAAM,OAAO,KAAK,QAAQ;AACrC,UAAM,IAAI,IAAI,IAAI,OAAO,KAAK,GAAG,CAAC;AAClC,UAAM,OAAO,SAAS,IAAI,IAAI,OAAO,OAAO,GAAG,CAAC,IAAK;AACrD,UAAM,QAAQ,OAAO,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE,IAAI;AAChD,UAAM,OAAO,YAAY,EAAE,OAAO,KAAK,KAAK,IAAI;AAChD,YAAQ,KAAK,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS,KAAK,GAAG,IAAI,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,MAAM,OAAO,aAAa,EAAE,aAAa,MAAM,KAAK,CAAC,CAAC;AAC7H,YAAQ,KAAK,KAAK,EAAE,IAAI,GAAG,EAAE,UAAU,KAAK,GAAG,IAAI,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,MAAM,KAAK,KAAK,SAAS,KAAK,GAAG,GAAG,MAAM,IAAI,QAAQ,KAAK,MAAM,SAAS,MAAM,MAAM,CAAC,CAAC;AAC3J,QAAI,MAAM;AACR,YAAM,IAAI,WAAW,MAAM,GAAG,CAAC,GAAG,IAAI,WAAW,GAAG,MAAM,CAAC;AAC3D,YAAM;AAAA,QACJ,MAAM;AAAA,UACJ,IAAI,GAAG,EAAE,SAAS,KAAK,GAAG;AAAA,UAC1B,IAAI,EAAE;AAAA,UAAG,IAAI,EAAE;AAAA,UAAG,IAAI,EAAE;AAAA,UAAG,IAAI,EAAE;AAAA,UACjC,MAAM;AAAA,UACN,MAAM,YAAY,EAAE,WAAW,KAAK,KAAK,IAAI;AAAA,UAC7C,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE;AAAA;AAAA,QAC9C,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,EAAE,WAAW;AACf,iBAAW,QAAQ,CAAC,QAAQ,OAAO,GAAY;AAC7C,YAAI,KAAK,IAAI,EAAG;AAChB,cAAM,IAAI,GAAG,EAAE,KAAK,SAAS,SAAS,KAAK,KAAK,KAAK,MAAM,EAAE,IAAI,KAAK,IAAI;AAC1E,cAAM,IAAI,WAAW,GAAG,GAAG,CAAC;AAC5B,cAAM,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,aAAa,KAAK,GAAG,IAAI,IAAI,IAAI,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,GAAG,MAAM,OAAO,MAAM,SAAS,QAAQ,KAAK,CAAC,CAAC;AAChJ,eAAO,KAAK,KAAK,EAAE,IAAI,GAAG,EAAE,SAAS,KAAK,GAAG,IAAI,IAAI,IAAI,GAAG,EAAE,GAAG,GAAG,EAAE,IAAI,GAAG,MAAM,UAAK,MAAM,IAAI,MAAM,OAAO,CAAC,CAAC;AAAA,MACnH;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,CAAC,MAAW,IAAI,IAAI,OAAO,CAAC,CAAC,KAAK,GAAG,IAAI,EAAE;AACvD,SAAO;AAAA,IACL,OAAO,CAAC,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM;AAAA,IACvC,KAAK,CAAC,MAAM,IAAI,IAAI,OAAO,CAAC,CAAC;AAAA,IAC7B,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,IACrC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,IACxC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;AAAA,IACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;AAAA,EACzC;AACF;AAMO,SAAS,mBAAmB,QAAoE;AACrG,MAAI,CAAC,OAAO,UAAU,OAAO,CAAC,KAAK,KAAM,QAAO;AAChD,QAAM,KAAK,CAACA,QAA+B,EAAE,KAAKA,IAAG,OAAO,OAAOA,EAAC,GAAsB,MAAM,MAAM,OAAO,KAAK;AAClH,QAAM,OAAO,GAAG,CAAC;AACjB,QAAM,QAAQ,CAAC,IAAI;AACnB,MAAI,IAAI;AACR,SAAO,MAAM,UAAU,IAAI,OAAO,QAAQ;AACxC,UAAM,IAAI,MAAM,MAAM;AACtB,eAAW,QAAQ,CAAC,QAAQ,OAAO,GAAY;AAC7C,UAAI,IAAI,OAAO,UAAU,OAAO,CAAC,KAAK,MAAM;AAC1C,UAAE,IAAI,IAAI,GAAG,CAAC;AACd,cAAM,KAAK,EAAE,IAAI,CAAE;AAAA,MACrB;AACA;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;",
|
|
6
|
+
"names": ["i"]
|
|
7
|
+
}
|
package/dist/easing.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Easing curves: t in [0, 1] → progress (may overshoot 1 for springy ones). */
|
|
2
|
+
export type Easing = (t: number) => number;
|
|
3
|
+
export declare const linear: Easing;
|
|
4
|
+
export declare const easeOutCubic: Easing;
|
|
5
|
+
export declare const easeInOutCubic: Easing;
|
|
6
|
+
/** Overshoots by ~8% then settles: the "bouncy" feel of playful UIs. */
|
|
7
|
+
export declare const easeOutBack: Easing;
|
|
8
|
+
/**
|
|
9
|
+
* A soft settle: overshoots by ~4% and comes back. Enough to feel physical
|
|
10
|
+
* and a little playful, not enough to look bouncy. The default feel of dsviz.
|
|
11
|
+
*/
|
|
12
|
+
export declare const easeOutSoft: Easing;
|
|
13
|
+
/** Speeds up like something falling. Pair with a short duration for gravity. */
|
|
14
|
+
export declare const easeInQuad: Easing;
|
package/dist/easing.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const linear = (t) => t;
|
|
2
|
+
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
|
|
3
|
+
const easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
4
|
+
const easeOutBack = (t) => {
|
|
5
|
+
const c1 = 1.4, c3 = c1 + 1;
|
|
6
|
+
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
7
|
+
};
|
|
8
|
+
const easeOutSoft = (t) => {
|
|
9
|
+
const c1 = 0.9, c3 = c1 + 1;
|
|
10
|
+
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
11
|
+
};
|
|
12
|
+
const easeInQuad = (t) => t * t;
|
|
13
|
+
export {
|
|
14
|
+
easeInOutCubic,
|
|
15
|
+
easeInQuad,
|
|
16
|
+
easeOutBack,
|
|
17
|
+
easeOutCubic,
|
|
18
|
+
easeOutSoft,
|
|
19
|
+
linear
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=easing.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/easing.ts"],
|
|
4
|
+
"sourcesContent": ["/** Easing curves: t in [0, 1] \u2192 progress (may overshoot 1 for springy ones). */\nexport type Easing = (t: number) => number;\n\nexport const linear: Easing = (t) => t;\nexport const easeOutCubic: Easing = (t) => 1 - Math.pow(1 - t, 3);\nexport const easeInOutCubic: Easing = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);\n\n/** Overshoots by ~8% then settles: the \"bouncy\" feel of playful UIs. */\nexport const easeOutBack: Easing = (t) => {\n const c1 = 1.4, c3 = c1 + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);\n};\n\n/**\n * A soft settle: overshoots by ~4% and comes back. Enough to feel physical\n * and a little playful, not enough to look bouncy. The default feel of dsviz.\n */\nexport const easeOutSoft: Easing = (t) => {\n const c1 = 0.9, c3 = c1 + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);\n};\n\n/** Speeds up like something falling. Pair with a short duration for gravity. */\nexport const easeInQuad: Easing = (t) => t * t;\n"],
|
|
5
|
+
"mappings": "AAGO,MAAM,SAAiB,CAAC,MAAM;AAC9B,MAAM,eAAuB,CAAC,MAAM,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC;AACzD,MAAM,iBAAyB,CAAC,MAAO,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,IAAI;AAG/F,MAAM,cAAsB,CAAC,MAAM;AACxC,QAAM,KAAK,KAAK,KAAK,KAAK;AAC1B,SAAO,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC;AAC7D;AAMO,MAAM,cAAsB,CAAC,MAAM;AACxC,QAAM,KAAK,KAAK,KAAK,KAAK;AAC1B,SAAO,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC;AAC7D;AAGO,MAAM,aAAqB,CAAC,MAAM,IAAI;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/fx.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Little celebrations, made from ordinary scene nodes.
|
|
3
|
+
*
|
|
4
|
+
* A burst is a ring of particles. Each particle is a circle whose `enter`
|
|
5
|
+
* says "start at the centre, radius 0", so the renderer flies it outward the
|
|
6
|
+
* moment it appears. Remove the particles from the scene a beat later and
|
|
7
|
+
* they fade out on their own (that's the normal exit tween). No special
|
|
8
|
+
* animation code: the keyed renderer already does everything we need.
|
|
9
|
+
*/
|
|
10
|
+
import { CircleNode, Tone } from './scene';
|
|
11
|
+
export interface BurstOptions {
|
|
12
|
+
/** Change this for every new burst (e.g. a counter) so the particles are new nodes. */
|
|
13
|
+
id: string;
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
count?: number;
|
|
17
|
+
/** How far the particles fly. Default 46. */
|
|
18
|
+
radius?: number;
|
|
19
|
+
tones?: Tone[];
|
|
20
|
+
}
|
|
21
|
+
export declare function burst(o: BurstOptions): CircleNode[];
|
package/dist/fx.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { circle } from "./scene.js";
|
|
2
|
+
const DEFAULT_TONES = ["active", "accent", "success", "visited", "danger"];
|
|
3
|
+
function burst(o) {
|
|
4
|
+
const n = o.count ?? 14;
|
|
5
|
+
const R = o.radius ?? 46;
|
|
6
|
+
const tones = o.tones ?? DEFAULT_TONES;
|
|
7
|
+
let seed = [...o.id].reduce((h, c) => h * 31 + c.charCodeAt(0) >>> 0, 7);
|
|
8
|
+
const rand = () => (seed = seed * 1664525 + 1013904223 >>> 0) / 2 ** 32;
|
|
9
|
+
return Array.from({ length: n }, (_, i) => {
|
|
10
|
+
const a = 2 * Math.PI * i / n + (rand() - 0.5) * 0.5;
|
|
11
|
+
const d = R * (0.65 + rand() * 0.5);
|
|
12
|
+
return circle({
|
|
13
|
+
id: `${o.id}:p${i}`,
|
|
14
|
+
x: o.x + Math.cos(a) * d,
|
|
15
|
+
y: o.y + Math.sin(a) * d,
|
|
16
|
+
r: 2 + rand() * 2.5,
|
|
17
|
+
role: "particle",
|
|
18
|
+
tone: tones[i % tones.length],
|
|
19
|
+
enter: { x: o.x, y: o.y, r: 0, opacity: 1 },
|
|
20
|
+
exit: { r: 0 }
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
burst
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=fx.js.map
|
package/dist/fx.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/fx.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Little celebrations, made from ordinary scene nodes.\n *\n * A burst is a ring of particles. Each particle is a circle whose `enter`\n * says \"start at the centre, radius 0\", so the renderer flies it outward the\n * moment it appears. Remove the particles from the scene a beat later and\n * they fade out on their own (that's the normal exit tween). No special\n * animation code: the keyed renderer already does everything we need.\n */\nimport { CircleNode, Tone, circle } from './scene';\n\nexport interface BurstOptions {\n /** Change this for every new burst (e.g. a counter) so the particles are new nodes. */\n id: string;\n x: number;\n y: number;\n count?: number;\n /** How far the particles fly. Default 46. */\n radius?: number;\n tones?: Tone[];\n}\n\nconst DEFAULT_TONES: Tone[] = ['active', 'accent', 'success', 'visited', 'danger'];\n\nexport function burst(o: BurstOptions): CircleNode[] {\n const n = o.count ?? 14;\n const R = o.radius ?? 46;\n const tones = o.tones ?? DEFAULT_TONES;\n // deterministic jitter from the id, so the same burst always looks the same\n let seed = [...o.id].reduce((h, c) => (h * 31 + c.charCodeAt(0)) >>> 0, 7);\n const rand = () => ((seed = (seed * 1664525 + 1013904223) >>> 0) / 2 ** 32);\n return Array.from({ length: n }, (_, i) => {\n const a = (2 * Math.PI * i) / n + (rand() - 0.5) * 0.5;\n const d = R * (0.65 + rand() * 0.5);\n return circle({\n id: `${o.id}:p${i}`,\n x: o.x + Math.cos(a) * d,\n y: o.y + Math.sin(a) * d,\n r: 2 + rand() * 2.5,\n role: 'particle',\n tone: tones[i % tones.length],\n enter: { x: o.x, y: o.y, r: 0, opacity: 1 },\n exit: { r: 0 },\n });\n });\n}\n"],
|
|
5
|
+
"mappings": "AASA,SAA2B,cAAc;AAazC,MAAM,gBAAwB,CAAC,UAAU,UAAU,WAAW,WAAW,QAAQ;AAE1E,SAAS,MAAM,GAA+B;AACnD,QAAM,IAAI,EAAE,SAAS;AACrB,QAAM,IAAI,EAAE,UAAU;AACtB,QAAM,QAAQ,EAAE,SAAS;AAEzB,MAAI,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,MAAO,IAAI,KAAK,EAAE,WAAW,CAAC,MAAO,GAAG,CAAC;AACzE,QAAM,OAAO,OAAQ,OAAQ,OAAO,UAAU,eAAgB,KAAK,KAAK;AACxE,SAAO,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,MAAM;AACzC,UAAM,IAAK,IAAI,KAAK,KAAK,IAAK,KAAK,KAAK,IAAI,OAAO;AACnD,UAAM,IAAI,KAAK,OAAO,KAAK,IAAI;AAC/B,WAAO,OAAO;AAAA,MACZ,IAAI,GAAG,EAAE,EAAE,KAAK,CAAC;AAAA,MACjB,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC,IAAI;AAAA,MACvB,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC,IAAI;AAAA,MACvB,GAAG,IAAI,KAAK,IAAI;AAAA,MAChB,MAAM;AAAA,MACN,MAAM,MAAM,IAAI,MAAM,MAAM;AAAA,MAC5B,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG,SAAS,EAAE;AAAA,MAC1C,MAAM,EAAE,GAAG,EAAE;AAAA,IACf,CAAC;AAAA,EACH,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plane geometry helpers. All take and return plain {x, y} points and work
|
|
3
|
+
* the same in pixel or world coordinates (nothing here assumes y up or down,
|
|
4
|
+
* except `signedArea`'s sign).
|
|
5
|
+
*/
|
|
6
|
+
import { Vec } from '../vec';
|
|
7
|
+
export declare const dot: (a: Vec, b: Vec) => number;
|
|
8
|
+
export declare const cross: (a: Vec, b: Vec) => number;
|
|
9
|
+
/** The (unsigned, ≤ π) angle at vertex B in the triangle A-B-C, in radians. */
|
|
10
|
+
export declare function angleAt(a: Vec, b: Vec, c: Vec): number;
|
|
11
|
+
export declare const toDegrees: (rad: number) => number;
|
|
12
|
+
/** Where the infinite lines AB and CD cross, or null if parallel. */
|
|
13
|
+
export declare function lineIntersection(a: Vec, b: Vec, c: Vec, d: Vec): Vec | null;
|
|
14
|
+
/** Intersection points of two circles: 0, 1 (tangent) or 2 points. */
|
|
15
|
+
export declare function circleIntersections(c1: Vec, r1: number, c2: Vec, r2: number): Vec[];
|
|
16
|
+
/** The foot of the perpendicular from P onto the line AB. */
|
|
17
|
+
export declare function footOfPerpendicular(p: Vec, a: Vec, b: Vec): Vec;
|
|
18
|
+
/** Circle through three points, or null if they are collinear. */
|
|
19
|
+
export declare function circumcircle(a: Vec, b: Vec, c: Vec): {
|
|
20
|
+
center: Vec;
|
|
21
|
+
r: number;
|
|
22
|
+
} | null;
|
|
23
|
+
/** Circle tangent to all three sides. */
|
|
24
|
+
export declare function incircle(a: Vec, b: Vec, c: Vec): {
|
|
25
|
+
center: Vec;
|
|
26
|
+
r: number;
|
|
27
|
+
};
|
|
28
|
+
export declare const centroid: (pts: readonly Vec[]) => Vec;
|
|
29
|
+
/** Intersection of the altitudes. */
|
|
30
|
+
export declare function orthocenter(a: Vec, b: Vec, c: Vec): Vec | null;
|
|
31
|
+
/** Shoelace formula. Positive when the points go counter-clockwise in a y-up system. */
|
|
32
|
+
export declare function signedArea(pts: readonly Vec[]): number;
|
|
33
|
+
/** Evenly spaced points on a circular arc from angle a0 to a1 (radians, counter-clockwise if a1 > a0). */
|
|
34
|
+
export declare function arcPoints(center: Vec, r: number, a0: number, a1: number, n: number): Array<[number, number]>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { V2, vadd, vdist, vscale, vsub } from "../vec.js";
|
|
2
|
+
const dot = (a, b) => a.x * b.x + a.y * b.y;
|
|
3
|
+
const cross = (a, b) => a.x * b.y - a.y * b.x;
|
|
4
|
+
function angleAt(a, b, c) {
|
|
5
|
+
const u = vsub(a, b), v = vsub(c, b);
|
|
6
|
+
return Math.atan2(Math.abs(cross(u, v)), dot(u, v));
|
|
7
|
+
}
|
|
8
|
+
const toDegrees = (rad) => rad * 180 / Math.PI;
|
|
9
|
+
function lineIntersection(a, b, c, d) {
|
|
10
|
+
const r = vsub(b, a), s = vsub(d, c);
|
|
11
|
+
const den = cross(r, s);
|
|
12
|
+
if (Math.abs(den) < 1e-12) return null;
|
|
13
|
+
const t = cross(vsub(c, a), s) / den;
|
|
14
|
+
return vadd(a, vscale(r, t));
|
|
15
|
+
}
|
|
16
|
+
function circleIntersections(c1, r1, c2, r2) {
|
|
17
|
+
const d = vdist(c1, c2);
|
|
18
|
+
if (d < 1e-12 || d > r1 + r2 + 1e-9 || d < Math.abs(r1 - r2) - 1e-9) return [];
|
|
19
|
+
const a = (r1 * r1 - r2 * r2 + d * d) / (2 * d);
|
|
20
|
+
const h = Math.sqrt(Math.max(0, r1 * r1 - a * a));
|
|
21
|
+
const ux = (c2.x - c1.x) / d, uy = (c2.y - c1.y) / d;
|
|
22
|
+
const m = V2(c1.x + a * ux, c1.y + a * uy);
|
|
23
|
+
if (h < 1e-9) return [m];
|
|
24
|
+
return [V2(m.x - h * uy, m.y + h * ux), V2(m.x + h * uy, m.y - h * ux)];
|
|
25
|
+
}
|
|
26
|
+
function footOfPerpendicular(p, a, b) {
|
|
27
|
+
const ab = vsub(b, a);
|
|
28
|
+
const t = dot(vsub(p, a), ab) / (dot(ab, ab) || 1);
|
|
29
|
+
return vadd(a, vscale(ab, t));
|
|
30
|
+
}
|
|
31
|
+
function circumcircle(a, b, c) {
|
|
32
|
+
const d = 2 * (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y));
|
|
33
|
+
if (Math.abs(d) < 1e-12) return null;
|
|
34
|
+
const a2 = a.x * a.x + a.y * a.y, b2 = b.x * b.x + b.y * b.y, c2 = c.x * c.x + c.y * c.y;
|
|
35
|
+
const center = V2((a2 * (b.y - c.y) + b2 * (c.y - a.y) + c2 * (a.y - b.y)) / d, (a2 * (c.x - b.x) + b2 * (a.x - c.x) + c2 * (b.x - a.x)) / d);
|
|
36
|
+
return { center, r: vdist(center, a) };
|
|
37
|
+
}
|
|
38
|
+
function incircle(a, b, c) {
|
|
39
|
+
const la = vdist(b, c), lb = vdist(a, c), lc = vdist(a, b);
|
|
40
|
+
const p = la + lb + lc || 1;
|
|
41
|
+
const center = V2((la * a.x + lb * b.x + lc * c.x) / p, (la * a.y + lb * b.y + lc * c.y) / p);
|
|
42
|
+
return { center, r: Math.abs(signedArea([a, b, c])) / (p / 2) };
|
|
43
|
+
}
|
|
44
|
+
const centroid = (pts) => vscale(pts.reduce((s, p) => vadd(s, p), V2(0, 0)), 1 / Math.max(1, pts.length));
|
|
45
|
+
function orthocenter(a, b, c) {
|
|
46
|
+
return lineIntersection(a, footOfPerpendicular(a, b, c), b, footOfPerpendicular(b, a, c));
|
|
47
|
+
}
|
|
48
|
+
function signedArea(pts) {
|
|
49
|
+
let s = 0;
|
|
50
|
+
for (let i = 0; i < pts.length; i++) s += cross(pts[i], pts[(i + 1) % pts.length]);
|
|
51
|
+
return s / 2;
|
|
52
|
+
}
|
|
53
|
+
function arcPoints(center, r, a0, a1, n) {
|
|
54
|
+
return Array.from({ length: n }, (_, i) => {
|
|
55
|
+
const t = a0 + (a1 - a0) * i / Math.max(1, n - 1);
|
|
56
|
+
return [center.x + r * Math.cos(t), center.y + r * Math.sin(t)];
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
angleAt,
|
|
61
|
+
arcPoints,
|
|
62
|
+
centroid,
|
|
63
|
+
circleIntersections,
|
|
64
|
+
circumcircle,
|
|
65
|
+
cross,
|
|
66
|
+
dot,
|
|
67
|
+
footOfPerpendicular,
|
|
68
|
+
incircle,
|
|
69
|
+
lineIntersection,
|
|
70
|
+
orthocenter,
|
|
71
|
+
signedArea,
|
|
72
|
+
toDegrees
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=math.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/geom/math.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Plane geometry helpers. All take and return plain {x, y} points and work\n * the same in pixel or world coordinates (nothing here assumes y up or down,\n * except `signedArea`'s sign).\n */\nimport { V2, Vec, vadd, vdist, vscale, vsub } from '../vec';\n\nexport const dot = (a: Vec, b: Vec) => a.x * b.x + a.y * b.y;\nexport const cross = (a: Vec, b: Vec) => a.x * b.y - a.y * b.x;\n\n/** The (unsigned, \u2264 \u03C0) angle at vertex B in the triangle A-B-C, in radians. */\nexport function angleAt(a: Vec, b: Vec, c: Vec): number {\n const u = vsub(a, b), v = vsub(c, b);\n return Math.atan2(Math.abs(cross(u, v)), dot(u, v));\n}\n\nexport const toDegrees = (rad: number) => (rad * 180) / Math.PI;\n\n/** Where the infinite lines AB and CD cross, or null if parallel. */\nexport function lineIntersection(a: Vec, b: Vec, c: Vec, d: Vec): Vec | null {\n const r = vsub(b, a), s = vsub(d, c);\n const den = cross(r, s);\n if (Math.abs(den) < 1e-12) return null;\n const t = cross(vsub(c, a), s) / den;\n return vadd(a, vscale(r, t));\n}\n\n/** Intersection points of two circles: 0, 1 (tangent) or 2 points. */\nexport function circleIntersections(c1: Vec, r1: number, c2: Vec, r2: number): Vec[] {\n const d = vdist(c1, c2);\n if (d < 1e-12 || d > r1 + r2 + 1e-9 || d < Math.abs(r1 - r2) - 1e-9) return [];\n const a = (r1 * r1 - r2 * r2 + d * d) / (2 * d);\n const h = Math.sqrt(Math.max(0, r1 * r1 - a * a));\n const ux = (c2.x - c1.x) / d, uy = (c2.y - c1.y) / d;\n const m = V2(c1.x + a * ux, c1.y + a * uy);\n if (h < 1e-9) return [m];\n return [V2(m.x - h * uy, m.y + h * ux), V2(m.x + h * uy, m.y - h * ux)];\n}\n\n/** The foot of the perpendicular from P onto the line AB. */\nexport function footOfPerpendicular(p: Vec, a: Vec, b: Vec): Vec {\n const ab = vsub(b, a);\n const t = dot(vsub(p, a), ab) / (dot(ab, ab) || 1);\n return vadd(a, vscale(ab, t));\n}\n\n/** Circle through three points, or null if they are collinear. */\nexport function circumcircle(a: Vec, b: Vec, c: Vec): { center: Vec; r: number } | null {\n const d = 2 * (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y));\n if (Math.abs(d) < 1e-12) return null;\n const a2 = a.x * a.x + a.y * a.y, b2 = b.x * b.x + b.y * b.y, c2 = c.x * c.x + c.y * c.y;\n const center = V2((a2 * (b.y - c.y) + b2 * (c.y - a.y) + c2 * (a.y - b.y)) / d, (a2 * (c.x - b.x) + b2 * (a.x - c.x) + c2 * (b.x - a.x)) / d);\n return { center, r: vdist(center, a) };\n}\n\n/** Circle tangent to all three sides. */\nexport function incircle(a: Vec, b: Vec, c: Vec): { center: Vec; r: number } {\n const la = vdist(b, c), lb = vdist(a, c), lc = vdist(a, b);\n const p = la + lb + lc || 1;\n const center = V2((la * a.x + lb * b.x + lc * c.x) / p, (la * a.y + lb * b.y + lc * c.y) / p);\n return { center, r: Math.abs(signedArea([a, b, c])) / (p / 2) };\n}\n\nexport const centroid = (pts: readonly Vec[]): Vec =>\n vscale(pts.reduce((s, p) => vadd(s, p), V2(0, 0)), 1 / Math.max(1, pts.length));\n\n/** Intersection of the altitudes. */\nexport function orthocenter(a: Vec, b: Vec, c: Vec): Vec | null {\n return lineIntersection(a, footOfPerpendicular(a, b, c), b, footOfPerpendicular(b, a, c));\n}\n\n/** Shoelace formula. Positive when the points go counter-clockwise in a y-up system. */\nexport function signedArea(pts: readonly Vec[]): number {\n let s = 0;\n for (let i = 0; i < pts.length; i++) s += cross(pts[i], pts[(i + 1) % pts.length]);\n return s / 2;\n}\n\n/** Evenly spaced points on a circular arc from angle a0 to a1 (radians, counter-clockwise if a1 > a0). */\nexport function arcPoints(center: Vec, r: number, a0: number, a1: number, n: number): Array<[number, number]> {\n return Array.from({ length: n }, (_, i) => {\n const t = a0 + ((a1 - a0) * i) / Math.max(1, n - 1);\n return [center.x + r * Math.cos(t), center.y + r * Math.sin(t)] as [number, number];\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Scene, Tone } from '../scene';
|
|
2
|
+
import { Vec } from '../vec';
|
|
3
|
+
export interface PolygonOptions {
|
|
4
|
+
id?: string;
|
|
5
|
+
tone?: Tone;
|
|
6
|
+
/** Vertex names (A, B, C …) placed just outside each corner. */
|
|
7
|
+
labels?: string[];
|
|
8
|
+
/** Give every vertex a drag handle; drag events carry `{ id, index }`. */
|
|
9
|
+
draggable?: boolean;
|
|
10
|
+
/** Fill the inside lightly. Default true. */
|
|
11
|
+
fill?: boolean;
|
|
12
|
+
width?: number;
|
|
13
|
+
/** Pulse the drag handles until the learner touches one. */
|
|
14
|
+
hint?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** A closed polygon with optional vertex labels and drag handles. */
|
|
17
|
+
export declare function polygon(points: readonly Vec[], o?: PolygonOptions): Scene;
|
|
18
|
+
export interface SegmentOptions {
|
|
19
|
+
id: string;
|
|
20
|
+
tone?: Tone;
|
|
21
|
+
dashed?: boolean;
|
|
22
|
+
width?: number;
|
|
23
|
+
/** Equal-length tick marks (1, 2 or 3) across the middle. */
|
|
24
|
+
ticks?: number;
|
|
25
|
+
label?: string;
|
|
26
|
+
}
|
|
27
|
+
/** A segment, optionally with equal-length tick marks and a label beside its midpoint. */
|
|
28
|
+
export declare function segment(a: Vec, b: Vec, o: SegmentOptions): Scene;
|
|
29
|
+
/** The little hash marks that say "these sides are equal". */
|
|
30
|
+
export declare function tickMarks(a: Vec, b: Vec, count: number, o: {
|
|
31
|
+
id: string;
|
|
32
|
+
tone?: Tone;
|
|
33
|
+
size?: number;
|
|
34
|
+
}): Scene;
|
|
35
|
+
export interface AngleMarkOptions {
|
|
36
|
+
id: string;
|
|
37
|
+
r?: number;
|
|
38
|
+
tone?: Tone;
|
|
39
|
+
/** Text at the arc, e.g. 'α', or true to show the measure in degrees. */
|
|
40
|
+
label?: string | boolean;
|
|
41
|
+
/** Draw a square for (near) right angles. Default true. */
|
|
42
|
+
rightSquare?: boolean;
|
|
43
|
+
/** How many arcs (1–3) to mark equal angles. Default 1. */
|
|
44
|
+
arcs?: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Mark the angle ABC at vertex B: an arc between rays BA and BC (always the
|
|
48
|
+
* smaller side), or a little square when the angle is 90°. The arc and the
|
|
49
|
+
* square have the same number of points so they morph into each other as you
|
|
50
|
+
* drag through 90°.
|
|
51
|
+
*/
|
|
52
|
+
export declare function angleMark(a: Vec, b: Vec, c: Vec, o: AngleMarkOptions): Scene;
|
|
53
|
+
/** A circle outline (and light fill) with an optional centre dot. */
|
|
54
|
+
export declare function circle(center: Vec, r: number, o: {
|
|
55
|
+
id: string;
|
|
56
|
+
tone?: Tone;
|
|
57
|
+
dashed?: boolean;
|
|
58
|
+
fill?: boolean;
|
|
59
|
+
showCenter?: boolean;
|
|
60
|
+
}): Scene;
|
|
61
|
+
/** A labelled point. */
|
|
62
|
+
export declare function pointLabel(p: Vec, s: string, o: {
|
|
63
|
+
id: string;
|
|
64
|
+
tone?: Tone;
|
|
65
|
+
offset?: Vec;
|
|
66
|
+
draggable?: boolean;
|
|
67
|
+
hint?: boolean;
|
|
68
|
+
data?: unknown;
|
|
69
|
+
}): Scene;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { handle } from "../handle.js";
|
|
2
|
+
import { circle as circleNode, path, text } from "../scene.js";
|
|
3
|
+
import { V2, vadd, vnorm, vscale, vsub } from "../vec.js";
|
|
4
|
+
import { angleAt, arcPoints, centroid, toDegrees } from "./math.js";
|
|
5
|
+
const pt = (v) => [v.x, v.y];
|
|
6
|
+
function polygon(points, o = {}) {
|
|
7
|
+
const id = o.id ?? "poly";
|
|
8
|
+
const nodes = [
|
|
9
|
+
path({ id: `${id}:shape`, points: points.map(pt), closed: true, role: o.fill === false ? "outline" : "region", tone: o.tone, width: o.width ?? 1.75 }),
|
|
10
|
+
path({ id: `${id}:edge`, points: points.map(pt), closed: true, role: "curve", tone: o.tone, width: o.width ?? 1.75 })
|
|
11
|
+
];
|
|
12
|
+
const c = centroid(points);
|
|
13
|
+
points.forEach((p, i) => {
|
|
14
|
+
if (o.labels?.[i]) {
|
|
15
|
+
const out = vadd(p, vscale(vnorm(vsub(p, c)), 16));
|
|
16
|
+
nodes.push(text({ id: `${id}:label:${i}`, x: out.x, y: out.y, text: o.labels[i], size: 15, role: "math" }));
|
|
17
|
+
}
|
|
18
|
+
nodes.push(...handle({ id: `${id}:v:${i}`, x: p.x, y: p.y, r: 5, draggable: !!o.draggable, hint: o.hint, tone: o.tone, data: { id, index: i } }));
|
|
19
|
+
});
|
|
20
|
+
return { nodes };
|
|
21
|
+
}
|
|
22
|
+
function segment(a, b, o) {
|
|
23
|
+
const nodes = [path({ id: o.id, points: [pt(a), pt(b)], role: "curve", tone: o.tone, dashed: o.dashed, width: o.width ?? 1.75 })];
|
|
24
|
+
if (o.ticks) nodes.push(...tickMarks(a, b, o.ticks, { id: `${o.id}:ticks`, tone: o.tone }).nodes);
|
|
25
|
+
if (o.label) {
|
|
26
|
+
const m = vscale(vadd(a, b), 0.5);
|
|
27
|
+
const n = vnorm(V2(-(b.y - a.y), b.x - a.x));
|
|
28
|
+
nodes.push(text({ id: `${o.id}:label`, x: m.x + n.x * 14, y: m.y + n.y * 14, text: o.label, size: 13, role: "math", tone: o.tone }));
|
|
29
|
+
}
|
|
30
|
+
return { nodes };
|
|
31
|
+
}
|
|
32
|
+
function tickMarks(a, b, count, o) {
|
|
33
|
+
const m = vscale(vadd(a, b), 0.5);
|
|
34
|
+
const d = vnorm(vsub(b, a));
|
|
35
|
+
const n = V2(-d.y, d.x);
|
|
36
|
+
const s = o.size ?? 6, gap = 4;
|
|
37
|
+
return {
|
|
38
|
+
nodes: Array.from({ length: count }, (_, i) => {
|
|
39
|
+
const c = vadd(m, vscale(d, (i - (count - 1) / 2) * gap));
|
|
40
|
+
return path({ id: `${o.id}:${i}`, points: [pt(vadd(c, vscale(n, -s))), pt(vadd(c, vscale(n, s)))], role: "curve", tone: o.tone, width: 1.5 });
|
|
41
|
+
})
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function angleMark(a, b, c, o) {
|
|
45
|
+
const r = o.r ?? 22;
|
|
46
|
+
const ang = angleAt(a, b, c);
|
|
47
|
+
const a0 = Math.atan2(a.y - b.y, a.x - b.x);
|
|
48
|
+
let a1 = Math.atan2(c.y - b.y, c.x - b.x);
|
|
49
|
+
let delta = a1 - a0;
|
|
50
|
+
while (delta > Math.PI) delta -= 2 * Math.PI;
|
|
51
|
+
while (delta < -Math.PI) delta += 2 * Math.PI;
|
|
52
|
+
a1 = a0 + delta;
|
|
53
|
+
const right = (o.rightSquare ?? true) && Math.abs(toDegrees(ang) - 90) < 0.5;
|
|
54
|
+
const nodes = [];
|
|
55
|
+
const N = 24;
|
|
56
|
+
const arcs = o.arcs ?? 1;
|
|
57
|
+
for (let k = 0; k < arcs; k++) {
|
|
58
|
+
const rr = r + k * 4;
|
|
59
|
+
let pts;
|
|
60
|
+
if (right) {
|
|
61
|
+
const s = rr * 0.7;
|
|
62
|
+
const u = V2(Math.cos(a0), Math.sin(a0)), v = V2(Math.cos(a1), Math.sin(a1));
|
|
63
|
+
const p1 = vadd(b, vscale(u, s)), p2 = vadd(b, vadd(vscale(u, s), vscale(v, s))), p3 = vadd(b, vscale(v, s));
|
|
64
|
+
pts = Array.from({ length: N }, (_, i) => {
|
|
65
|
+
const t = i / (N - 1) * 2;
|
|
66
|
+
const q = t < 1 ? vadd(p1, vscale(vsub(p2, p1), t)) : vadd(p2, vscale(vsub(p3, p2), t - 1));
|
|
67
|
+
return pt(q);
|
|
68
|
+
});
|
|
69
|
+
} else pts = arcPoints(b, rr, a0, a1, N);
|
|
70
|
+
nodes.push(path({ id: `${o.id}:arc:${k}`, points: pts, role: "curve", tone: o.tone, width: 1.5 }));
|
|
71
|
+
}
|
|
72
|
+
nodes.unshift(path({ id: `${o.id}:wedge`, points: [pt(b), ...arcPoints(b, r, a0, a1, N)], closed: true, role: "region", tone: o.tone }));
|
|
73
|
+
if (o.label) {
|
|
74
|
+
const mid = (a0 + a1) / 2;
|
|
75
|
+
const lr = r + 12 + (arcs - 1) * 4;
|
|
76
|
+
const s = o.label === true ? `${Math.round(toDegrees(ang))}\xB0` : o.label;
|
|
77
|
+
nodes.push(text({ id: `${o.id}:label`, x: b.x + lr * Math.cos(mid), y: b.y + lr * Math.sin(mid), text: s, size: 14, role: "math", tone: o.tone }));
|
|
78
|
+
}
|
|
79
|
+
return { nodes };
|
|
80
|
+
}
|
|
81
|
+
function circle(center, r, o) {
|
|
82
|
+
const nodes = [
|
|
83
|
+
circleNode({ id: o.id, x: center.x, y: center.y, r, tone: o.tone ?? "ghost", dashed: o.dashed, role: o.fill ? void 0 : "outline" })
|
|
84
|
+
];
|
|
85
|
+
if (o.showCenter) nodes.push(circleNode({ id: `${o.id}:c`, x: center.x, y: center.y, r: 2.5, role: "dot", tone: o.tone }));
|
|
86
|
+
return { nodes };
|
|
87
|
+
}
|
|
88
|
+
function pointLabel(p, s, o) {
|
|
89
|
+
const off = o.offset ?? V2(10, -12);
|
|
90
|
+
return {
|
|
91
|
+
nodes: [
|
|
92
|
+
...handle({ id: o.id, x: p.x, y: p.y, r: 4.5, tone: o.tone, draggable: !!o.draggable, hint: o.hint, data: o.data ?? { id: o.id } }),
|
|
93
|
+
text({ id: `${o.id}:label`, x: p.x + off.x, y: p.y + off.y, text: s, size: 14, role: "math", tone: o.tone })
|
|
94
|
+
]
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export {
|
|
98
|
+
angleMark,
|
|
99
|
+
circle,
|
|
100
|
+
pointLabel,
|
|
101
|
+
polygon,
|
|
102
|
+
segment,
|
|
103
|
+
tickMarks
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=shapes.js.map
|