@redwilly/anima 0.1.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/dist/cli/SceneLoader.d.ts +22 -0
- package/dist/cli/SceneLoader.js +47 -0
- package/dist/cli/commands/export-frame.d.ts +13 -0
- package/dist/cli/commands/export-frame.js +60 -0
- package/dist/cli/commands/list-scenes.d.ts +5 -0
- package/dist/cli/commands/list-scenes.js +22 -0
- package/dist/cli/commands/preview.d.ts +5 -0
- package/dist/cli/commands/preview.js +11 -0
- package/dist/cli/commands/render.d.ts +16 -0
- package/dist/cli/commands/render.js +76 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +63 -0
- package/dist/core/animations/Animation.d.ts +41 -0
- package/dist/core/animations/Animation.js +76 -0
- package/dist/core/animations/camera/Follow.d.ts +70 -0
- package/dist/core/animations/camera/Follow.js +69 -0
- package/dist/core/animations/camera/Shake.d.ts +90 -0
- package/dist/core/animations/camera/Shake.js +87 -0
- package/dist/core/animations/camera/index.d.ts +2 -0
- package/dist/core/animations/camera/index.js +2 -0
- package/dist/core/animations/categories/ExitAnimation.d.ts +17 -0
- package/dist/core/animations/categories/ExitAnimation.js +15 -0
- package/dist/core/animations/categories/IntroductoryAnimation.d.ts +16 -0
- package/dist/core/animations/categories/IntroductoryAnimation.js +14 -0
- package/dist/core/animations/categories/TransformativeAnimation.d.ts +25 -0
- package/dist/core/animations/categories/TransformativeAnimation.js +25 -0
- package/dist/core/animations/categories/index.d.ts +3 -0
- package/dist/core/animations/categories/index.js +3 -0
- package/dist/core/animations/composition/Parallel.d.ts +37 -0
- package/dist/core/animations/composition/Parallel.js +79 -0
- package/dist/core/animations/composition/Sequence.d.ts +41 -0
- package/dist/core/animations/composition/Sequence.js +95 -0
- package/dist/core/animations/composition/index.d.ts +2 -0
- package/dist/core/animations/composition/index.js +3 -0
- package/dist/core/animations/draw/Draw.d.ts +30 -0
- package/dist/core/animations/draw/Draw.js +122 -0
- package/dist/core/animations/draw/Unwrite.d.ts +30 -0
- package/dist/core/animations/draw/Unwrite.js +120 -0
- package/dist/core/animations/draw/Write.d.ts +35 -0
- package/dist/core/animations/draw/Write.js +119 -0
- package/dist/core/animations/draw/index.d.ts +3 -0
- package/dist/core/animations/draw/index.js +3 -0
- package/dist/core/animations/draw/partialPath.d.ts +6 -0
- package/dist/core/animations/draw/partialPath.js +138 -0
- package/dist/core/animations/easing/bounce.d.ts +13 -0
- package/dist/core/animations/easing/bounce.js +37 -0
- package/dist/core/animations/easing/index.d.ts +7 -0
- package/dist/core/animations/easing/index.js +11 -0
- package/dist/core/animations/easing/manim.d.ts +46 -0
- package/dist/core/animations/easing/manim.js +102 -0
- package/dist/core/animations/easing/registry.d.ts +8 -0
- package/dist/core/animations/easing/registry.js +25 -0
- package/dist/core/animations/easing/standard.d.ts +113 -0
- package/dist/core/animations/easing/standard.js +151 -0
- package/dist/core/animations/easing/types.d.ts +6 -0
- package/dist/core/animations/easing/types.js +0 -0
- package/dist/core/animations/fade/FadeIn.d.ts +17 -0
- package/dist/core/animations/fade/FadeIn.js +22 -0
- package/dist/core/animations/fade/FadeOut.d.ts +17 -0
- package/dist/core/animations/fade/FadeOut.js +23 -0
- package/dist/core/animations/fade/index.d.ts +2 -0
- package/dist/core/animations/fade/index.js +2 -0
- package/dist/core/animations/index.d.ts +11 -0
- package/dist/core/animations/index.js +17 -0
- package/dist/core/animations/keyframes/KeyframeAnimation.d.ts +33 -0
- package/dist/core/animations/keyframes/KeyframeAnimation.js +40 -0
- package/dist/core/animations/keyframes/KeyframeTrack.d.ts +31 -0
- package/dist/core/animations/keyframes/KeyframeTrack.js +83 -0
- package/dist/core/animations/keyframes/index.d.ts +4 -0
- package/dist/core/animations/keyframes/index.js +5 -0
- package/dist/core/animations/keyframes/types.d.ts +25 -0
- package/dist/core/animations/keyframes/types.js +6 -0
- package/dist/core/animations/morph/MorphTo.d.ts +22 -0
- package/dist/core/animations/morph/MorphTo.js +42 -0
- package/dist/core/animations/morph/index.d.ts +1 -0
- package/dist/core/animations/morph/index.js +1 -0
- package/dist/core/animations/transform/MoveTo.d.ts +24 -0
- package/dist/core/animations/transform/MoveTo.js +38 -0
- package/dist/core/animations/transform/Rotate.d.ts +23 -0
- package/dist/core/animations/transform/Rotate.js +34 -0
- package/dist/core/animations/transform/Scale.d.ts +23 -0
- package/dist/core/animations/transform/Scale.js +35 -0
- package/dist/core/animations/transform/index.d.ts +3 -0
- package/dist/core/animations/transform/index.js +3 -0
- package/dist/core/animations/types.d.ts +52 -0
- package/dist/core/animations/types.js +6 -0
- package/dist/core/camera/Camera.d.ts +87 -0
- package/dist/core/camera/Camera.js +175 -0
- package/dist/core/camera/CameraFrame.d.ts +242 -0
- package/dist/core/camera/CameraFrame.js +322 -0
- package/dist/core/camera/index.d.ts +4 -0
- package/dist/core/camera/index.js +3 -0
- package/dist/core/camera/types.d.ts +17 -0
- package/dist/core/camera/types.js +1 -0
- package/dist/core/errors/AnimationErrors.d.ts +12 -0
- package/dist/core/errors/AnimationErrors.js +37 -0
- package/dist/core/errors/index.d.ts +1 -0
- package/dist/core/errors/index.js +1 -0
- package/dist/core/math/Vector2/Vector2.d.ts +23 -0
- package/dist/core/math/Vector2/Vector2.js +46 -0
- package/dist/core/math/Vector2/index.d.ts +1 -0
- package/dist/core/math/Vector2/index.js +1 -0
- package/dist/core/math/bezier/BezierPath.d.ts +38 -0
- package/dist/core/math/bezier/BezierPath.js +264 -0
- package/dist/core/math/bezier/evaluators.d.ts +9 -0
- package/dist/core/math/bezier/evaluators.js +36 -0
- package/dist/core/math/bezier/index.d.ts +8 -0
- package/dist/core/math/bezier/index.js +6 -0
- package/dist/core/math/bezier/length.d.ts +5 -0
- package/dist/core/math/bezier/length.js +27 -0
- package/dist/core/math/bezier/morphing.d.ts +16 -0
- package/dist/core/math/bezier/morphing.js +151 -0
- package/dist/core/math/bezier/sampling.d.ts +7 -0
- package/dist/core/math/bezier/sampling.js +153 -0
- package/dist/core/math/bezier/split.d.ts +19 -0
- package/dist/core/math/bezier/split.js +44 -0
- package/dist/core/math/bezier/types.d.ts +8 -0
- package/dist/core/math/bezier/types.js +0 -0
- package/dist/core/math/color/Color.d.ts +28 -0
- package/dist/core/math/color/Color.js +60 -0
- package/dist/core/math/color/conversions.d.ts +17 -0
- package/dist/core/math/color/conversions.js +100 -0
- package/dist/core/math/color/index.d.ts +2 -0
- package/dist/core/math/color/index.js +2 -0
- package/dist/core/math/index.d.ts +4 -0
- package/dist/core/math/index.js +5 -0
- package/dist/core/math/matrix/Matrix3x3.d.ts +23 -0
- package/dist/core/math/matrix/Matrix3x3.js +91 -0
- package/dist/core/math/matrix/factories.d.ts +12 -0
- package/dist/core/math/matrix/factories.js +44 -0
- package/dist/core/math/matrix/index.d.ts +2 -0
- package/dist/core/math/matrix/index.js +2 -0
- package/dist/core/renderer/FrameRenderer.d.ts +37 -0
- package/dist/core/renderer/FrameRenderer.js +75 -0
- package/dist/core/renderer/ProgressReporter.d.ts +19 -0
- package/dist/core/renderer/ProgressReporter.js +58 -0
- package/dist/core/renderer/Renderer.d.ts +36 -0
- package/dist/core/renderer/Renderer.js +102 -0
- package/dist/core/renderer/drawMobject.d.ts +8 -0
- package/dist/core/renderer/drawMobject.js +109 -0
- package/dist/core/renderer/formats/index.d.ts +3 -0
- package/dist/core/renderer/formats/index.js +3 -0
- package/dist/core/renderer/formats/png.d.ts +5 -0
- package/dist/core/renderer/formats/png.js +7 -0
- package/dist/core/renderer/formats/sprite.d.ts +6 -0
- package/dist/core/renderer/formats/sprite.js +24 -0
- package/dist/core/renderer/formats/video.d.ts +8 -0
- package/dist/core/renderer/formats/video.js +51 -0
- package/dist/core/renderer/index.d.ts +7 -0
- package/dist/core/renderer/index.js +9 -0
- package/dist/core/renderer/types.d.ts +87 -0
- package/dist/core/renderer/types.js +13 -0
- package/dist/core/scene/Scene.d.ts +104 -0
- package/dist/core/scene/Scene.js +225 -0
- package/dist/core/scene/index.d.ts +2 -0
- package/dist/core/scene/index.js +1 -0
- package/dist/core/scene/types.d.ts +23 -0
- package/dist/core/scene/types.js +0 -0
- package/dist/core/serialization/animation.d.ts +23 -0
- package/dist/core/serialization/animation.js +176 -0
- package/dist/core/serialization/easingLookup.d.ts +13 -0
- package/dist/core/serialization/easingLookup.js +65 -0
- package/dist/core/serialization/index.d.ts +23 -0
- package/dist/core/serialization/index.js +29 -0
- package/dist/core/serialization/mobject.d.ts +23 -0
- package/dist/core/serialization/mobject.js +248 -0
- package/dist/core/serialization/prettyPrint.d.ts +12 -0
- package/dist/core/serialization/prettyPrint.js +16 -0
- package/dist/core/serialization/primitives.d.ts +24 -0
- package/dist/core/serialization/primitives.js +98 -0
- package/dist/core/serialization/registry.d.ts +29 -0
- package/dist/core/serialization/registry.js +39 -0
- package/dist/core/serialization/scene.d.ts +28 -0
- package/dist/core/serialization/scene.js +114 -0
- package/dist/core/serialization/types.d.ts +152 -0
- package/dist/core/serialization/types.js +6 -0
- package/dist/core/timeline/Timeline.d.ts +70 -0
- package/dist/core/timeline/Timeline.js +144 -0
- package/dist/core/timeline/index.d.ts +5 -0
- package/dist/core/timeline/index.js +4 -0
- package/dist/core/timeline/types.d.ts +29 -0
- package/dist/core/timeline/types.js +0 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +22 -0
- package/dist/mobjects/Mobject.d.ts +98 -0
- package/dist/mobjects/Mobject.js +343 -0
- package/dist/mobjects/VGroup/VGroup.d.ts +51 -0
- package/dist/mobjects/VGroup/VGroup.js +142 -0
- package/dist/mobjects/VGroup/index.d.ts +3 -0
- package/dist/mobjects/VGroup/index.js +2 -0
- package/dist/mobjects/VGroup/layout.d.ts +20 -0
- package/dist/mobjects/VGroup/layout.js +139 -0
- package/dist/mobjects/VMobject.d.ts +106 -0
- package/dist/mobjects/VMobject.js +216 -0
- package/dist/mobjects/geometry/Arc.d.ts +8 -0
- package/dist/mobjects/geometry/Arc.js +46 -0
- package/dist/mobjects/geometry/Arrow.d.ts +7 -0
- package/dist/mobjects/geometry/Arrow.js +34 -0
- package/dist/mobjects/geometry/Circle.d.ts +4 -0
- package/dist/mobjects/geometry/Circle.js +10 -0
- package/dist/mobjects/geometry/Line.d.ts +8 -0
- package/dist/mobjects/geometry/Line.js +19 -0
- package/dist/mobjects/geometry/Point.d.ts +5 -0
- package/dist/mobjects/geometry/Point.js +11 -0
- package/dist/mobjects/geometry/Polygon.d.ts +7 -0
- package/dist/mobjects/geometry/Polygon.js +21 -0
- package/dist/mobjects/geometry/Rectangle.d.ts +6 -0
- package/dist/mobjects/geometry/Rectangle.js +18 -0
- package/dist/mobjects/geometry/index.d.ts +7 -0
- package/dist/mobjects/geometry/index.js +7 -0
- package/dist/mobjects/graph/Graph.d.ts +28 -0
- package/dist/mobjects/graph/Graph.js +119 -0
- package/dist/mobjects/graph/GraphEdge.d.ts +26 -0
- package/dist/mobjects/graph/GraphEdge.js +64 -0
- package/dist/mobjects/graph/GraphNode.d.ts +19 -0
- package/dist/mobjects/graph/GraphNode.js +63 -0
- package/dist/mobjects/graph/index.d.ts +5 -0
- package/dist/mobjects/graph/index.js +5 -0
- package/dist/mobjects/graph/layouts/circular.d.ts +8 -0
- package/dist/mobjects/graph/layouts/circular.js +23 -0
- package/dist/mobjects/graph/layouts/forceDirected.d.ts +9 -0
- package/dist/mobjects/graph/layouts/forceDirected.js +102 -0
- package/dist/mobjects/graph/layouts/index.d.ts +3 -0
- package/dist/mobjects/graph/layouts/index.js +3 -0
- package/dist/mobjects/graph/layouts/tree.d.ts +9 -0
- package/dist/mobjects/graph/layouts/tree.js +99 -0
- package/dist/mobjects/graph/types.d.ts +35 -0
- package/dist/mobjects/graph/types.js +0 -0
- package/dist/mobjects/index.d.ts +6 -0
- package/dist/mobjects/index.js +6 -0
- package/dist/mobjects/text/Glyph.d.ts +11 -0
- package/dist/mobjects/text/Glyph.js +72 -0
- package/dist/mobjects/text/Text.d.ts +19 -0
- package/dist/mobjects/text/Text.js +76 -0
- package/dist/mobjects/text/index.d.ts +4 -0
- package/dist/mobjects/text/index.js +3 -0
- package/dist/mobjects/text/types.d.ts +12 -0
- package/dist/mobjects/text/types.js +8 -0
- package/package.json +51 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { VGroup } from '../VGroup';
|
|
2
|
+
import { GraphNode } from './GraphNode';
|
|
3
|
+
import { GraphEdge } from './GraphEdge';
|
|
4
|
+
import type { GraphNodeId, NodeConfig, EdgeConfig, LayoutType, LayoutConfig } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* A graph structure containing nodes and edges.
|
|
7
|
+
* Manages nodes as VMobjects and edges as BezierPath curves.
|
|
8
|
+
* Supports multiple layout algorithms for automatic positioning.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Graph extends VGroup {
|
|
11
|
+
private nodes;
|
|
12
|
+
private edges;
|
|
13
|
+
constructor();
|
|
14
|
+
/** Adds a node to the graph. */
|
|
15
|
+
addNode(id: GraphNodeId, config?: NodeConfig): GraphNode;
|
|
16
|
+
/** Removes a node and all connected edges from the graph. */
|
|
17
|
+
removeNode(id: GraphNodeId): this;
|
|
18
|
+
getNode(id: GraphNodeId): GraphNode | undefined;
|
|
19
|
+
getNodes(): GraphNode[];
|
|
20
|
+
addEdge(sourceId: GraphNodeId, targetId: GraphNodeId, config?: EdgeConfig): GraphEdge | undefined;
|
|
21
|
+
removeEdge(sourceId: GraphNodeId, targetId: GraphNodeId): this;
|
|
22
|
+
private removeEdgeInternal;
|
|
23
|
+
getEdgePath(sourceId: GraphNodeId, targetId: GraphNodeId): ReturnType<GraphEdge['getPath']>;
|
|
24
|
+
getEdges(): GraphEdge[];
|
|
25
|
+
/** Applies a layout algorithm to reposition all nodes. */
|
|
26
|
+
layout(type: LayoutType, config?: LayoutConfig): this;
|
|
27
|
+
updateEdges(): this;
|
|
28
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { VGroup } from '../VGroup';
|
|
2
|
+
import { GraphNode } from './GraphNode';
|
|
3
|
+
import { GraphEdge } from './GraphEdge';
|
|
4
|
+
import { circularLayout, treeLayout, forceDirectedLayout } from './layouts';
|
|
5
|
+
/**
|
|
6
|
+
* A graph structure containing nodes and edges.
|
|
7
|
+
* Manages nodes as VMobjects and edges as BezierPath curves.
|
|
8
|
+
* Supports multiple layout algorithms for automatic positioning.
|
|
9
|
+
*/
|
|
10
|
+
export class Graph extends VGroup {
|
|
11
|
+
nodes = new Map();
|
|
12
|
+
edges = [];
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
}
|
|
16
|
+
/** Adds a node to the graph. */
|
|
17
|
+
addNode(id, config = {}) {
|
|
18
|
+
if (this.nodes.has(id)) {
|
|
19
|
+
const existing = this.nodes.get(id);
|
|
20
|
+
if (existing)
|
|
21
|
+
return existing;
|
|
22
|
+
}
|
|
23
|
+
const node = new GraphNode(id, config);
|
|
24
|
+
this.nodes.set(id, node);
|
|
25
|
+
this.add(node);
|
|
26
|
+
return node;
|
|
27
|
+
}
|
|
28
|
+
/** Removes a node and all connected edges from the graph. */
|
|
29
|
+
removeNode(id) {
|
|
30
|
+
const node = this.nodes.get(id);
|
|
31
|
+
if (!node)
|
|
32
|
+
return this;
|
|
33
|
+
// Remove connected edges
|
|
34
|
+
const connectedEdges = this.edges.filter(e => e.source === id || e.target === id);
|
|
35
|
+
for (const edge of connectedEdges) {
|
|
36
|
+
this.removeEdgeInternal(edge);
|
|
37
|
+
}
|
|
38
|
+
// Remove node
|
|
39
|
+
this.nodes.delete(id);
|
|
40
|
+
this.remove(node);
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
getNode(id) {
|
|
44
|
+
return this.nodes.get(id);
|
|
45
|
+
}
|
|
46
|
+
getNodes() {
|
|
47
|
+
return Array.from(this.nodes.values());
|
|
48
|
+
}
|
|
49
|
+
addEdge(sourceId, targetId, config = {}) {
|
|
50
|
+
const sourceNode = this.nodes.get(sourceId);
|
|
51
|
+
const targetNode = this.nodes.get(targetId);
|
|
52
|
+
if (!sourceNode || !targetNode) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const existing = this.edges.find(e => (e.source === sourceId && e.target === targetId) ||
|
|
56
|
+
(e.source === targetId && e.target === sourceId));
|
|
57
|
+
if (existing)
|
|
58
|
+
return existing;
|
|
59
|
+
const edge = new GraphEdge(sourceNode, targetNode, config);
|
|
60
|
+
this.edges.push(edge);
|
|
61
|
+
this.add(edge);
|
|
62
|
+
return edge;
|
|
63
|
+
}
|
|
64
|
+
removeEdge(sourceId, targetId) {
|
|
65
|
+
const edge = this.edges.find(e => (e.source === sourceId && e.target === targetId) ||
|
|
66
|
+
(e.source === targetId && e.target === sourceId));
|
|
67
|
+
if (edge) {
|
|
68
|
+
this.removeEdgeInternal(edge);
|
|
69
|
+
}
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
removeEdgeInternal(edge) {
|
|
73
|
+
const index = this.edges.indexOf(edge);
|
|
74
|
+
if (index > -1) {
|
|
75
|
+
this.edges.splice(index, 1);
|
|
76
|
+
this.remove(edge);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
getEdgePath(sourceId, targetId) {
|
|
80
|
+
const edge = this.edges.find(e => (e.source === sourceId && e.target === targetId) ||
|
|
81
|
+
(e.source === targetId && e.target === sourceId));
|
|
82
|
+
return edge?.getPath();
|
|
83
|
+
}
|
|
84
|
+
getEdges() {
|
|
85
|
+
return [...this.edges];
|
|
86
|
+
}
|
|
87
|
+
/** Applies a layout algorithm to reposition all nodes. */
|
|
88
|
+
layout(type, config = {}) {
|
|
89
|
+
const nodeArray = this.getNodes();
|
|
90
|
+
let positions;
|
|
91
|
+
switch (type) {
|
|
92
|
+
case 'circular':
|
|
93
|
+
positions = circularLayout(nodeArray, config);
|
|
94
|
+
break;
|
|
95
|
+
case 'tree':
|
|
96
|
+
positions = treeLayout(nodeArray, this.edges, config);
|
|
97
|
+
break;
|
|
98
|
+
case 'force-directed':
|
|
99
|
+
positions = forceDirectedLayout(nodeArray, this.edges, config);
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
positions = new Map();
|
|
103
|
+
}
|
|
104
|
+
for (const [id, position] of positions) {
|
|
105
|
+
const node = this.nodes.get(id);
|
|
106
|
+
if (node) {
|
|
107
|
+
node.pos(position.x, position.y);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
this.updateEdges();
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
updateEdges() {
|
|
114
|
+
for (const edge of this.edges) {
|
|
115
|
+
edge.updatePath();
|
|
116
|
+
}
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { VMobject } from '../VMobject';
|
|
2
|
+
import { BezierPath } from '../../core/math/bezier/BezierPath';
|
|
3
|
+
import type { GraphNodeId, EdgeConfig } from './types';
|
|
4
|
+
import type { GraphNode } from './GraphNode';
|
|
5
|
+
/**
|
|
6
|
+
* A graph edge represented as a BezierPath connecting two nodes.
|
|
7
|
+
* Supports straight or curved edges with customizable styling.
|
|
8
|
+
*/
|
|
9
|
+
export declare class GraphEdge extends VMobject {
|
|
10
|
+
readonly source: GraphNodeId;
|
|
11
|
+
readonly target: GraphNodeId;
|
|
12
|
+
private sourceNode;
|
|
13
|
+
private targetNode;
|
|
14
|
+
private curved;
|
|
15
|
+
constructor(sourceNode: GraphNode, targetNode: GraphNode, config?: EdgeConfig);
|
|
16
|
+
/**
|
|
17
|
+
* Recalculates the edge path based on current node positions.
|
|
18
|
+
* Call this when nodes move to update the edge connection.
|
|
19
|
+
*/
|
|
20
|
+
updatePath(): void;
|
|
21
|
+
getPath(): BezierPath | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Updates node references (used when nodes are replaced).
|
|
24
|
+
*/
|
|
25
|
+
setNodes(sourceNode: GraphNode, targetNode: GraphNode): void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { VMobject } from '../VMobject';
|
|
2
|
+
import { BezierPath } from '../../core/math/bezier/BezierPath';
|
|
3
|
+
import { Vector2 } from '../../core/math/Vector2/Vector2';
|
|
4
|
+
import { Color } from '../../core/math/color/Color';
|
|
5
|
+
/**
|
|
6
|
+
* A graph edge represented as a BezierPath connecting two nodes.
|
|
7
|
+
* Supports straight or curved edges with customizable styling.
|
|
8
|
+
*/
|
|
9
|
+
export class GraphEdge extends VMobject {
|
|
10
|
+
source;
|
|
11
|
+
target;
|
|
12
|
+
sourceNode;
|
|
13
|
+
targetNode;
|
|
14
|
+
curved;
|
|
15
|
+
constructor(sourceNode, targetNode, config = {}) {
|
|
16
|
+
super();
|
|
17
|
+
this.source = sourceNode.id;
|
|
18
|
+
this.target = targetNode.id;
|
|
19
|
+
this.sourceNode = sourceNode;
|
|
20
|
+
this.targetNode = targetNode;
|
|
21
|
+
this.curved = config.curved ?? false;
|
|
22
|
+
// Apply styling
|
|
23
|
+
this.strokeColor = config.strokeColor ?? Color.WHITE;
|
|
24
|
+
this.strokeWidth = config.strokeWidth ?? 2;
|
|
25
|
+
this.fillColor = Color.TRANSPARENT;
|
|
26
|
+
this.fillOpacity = 0;
|
|
27
|
+
this.updatePath();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Recalculates the edge path based on current node positions.
|
|
31
|
+
* Call this when nodes move to update the edge connection.
|
|
32
|
+
*/
|
|
33
|
+
updatePath() {
|
|
34
|
+
const startPos = this.sourceNode.getCenter();
|
|
35
|
+
const endPos = this.targetNode.getCenter();
|
|
36
|
+
const path = new BezierPath();
|
|
37
|
+
path.moveTo(startPos);
|
|
38
|
+
if (this.curved) {
|
|
39
|
+
// Create a curved path using quadratic Bezier
|
|
40
|
+
const mid = startPos.lerp(endPos, 0.5);
|
|
41
|
+
const direction = endPos.subtract(startPos);
|
|
42
|
+
const perpendicular = new Vector2(-direction.y, direction.x).normalize();
|
|
43
|
+
const curveOffset = direction.length() * 0.2;
|
|
44
|
+
const controlPoint = mid.add(perpendicular.multiply(curveOffset));
|
|
45
|
+
path.quadraticTo(controlPoint, endPos);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Straight line
|
|
49
|
+
path.lineTo(endPos);
|
|
50
|
+
}
|
|
51
|
+
this.pathList = [path];
|
|
52
|
+
}
|
|
53
|
+
getPath() {
|
|
54
|
+
return this.pathList[0];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Updates node references (used when nodes are replaced).
|
|
58
|
+
*/
|
|
59
|
+
setNodes(sourceNode, targetNode) {
|
|
60
|
+
this.sourceNode = sourceNode;
|
|
61
|
+
this.targetNode = targetNode;
|
|
62
|
+
this.updatePath();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { VMobject } from '../VMobject';
|
|
2
|
+
import { Vector2 } from '../../core/math/Vector2/Vector2';
|
|
3
|
+
import type { GraphNodeId, NodeConfig } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* A graph node represented as a circular VMobject.
|
|
6
|
+
* Supports customizable radius, stroke, and fill styling.
|
|
7
|
+
*/
|
|
8
|
+
export declare class GraphNode extends VMobject {
|
|
9
|
+
readonly id: GraphNodeId;
|
|
10
|
+
private nodeRadius;
|
|
11
|
+
constructor(id: GraphNodeId, config?: NodeConfig);
|
|
12
|
+
get radius(): number;
|
|
13
|
+
/**
|
|
14
|
+
* Generates a circular BezierPath approximation using cubic Bezier curves.
|
|
15
|
+
* Uses the standard 4-point circle approximation with control point factor ~0.5523.
|
|
16
|
+
*/
|
|
17
|
+
private generateCirclePath;
|
|
18
|
+
getCenter(): Vector2;
|
|
19
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { VMobject } from '../VMobject';
|
|
2
|
+
import { BezierPath } from '../../core/math/bezier/BezierPath';
|
|
3
|
+
import { Vector2 } from '../../core/math/Vector2/Vector2';
|
|
4
|
+
const DEFAULT_RADIUS = 0.25;
|
|
5
|
+
/**
|
|
6
|
+
* A graph node represented as a circular VMobject.
|
|
7
|
+
* Supports customizable radius, stroke, and fill styling.
|
|
8
|
+
*/
|
|
9
|
+
export class GraphNode extends VMobject {
|
|
10
|
+
id;
|
|
11
|
+
nodeRadius;
|
|
12
|
+
constructor(id, config = {}) {
|
|
13
|
+
super();
|
|
14
|
+
this.id = id;
|
|
15
|
+
this.nodeRadius = config.radius ?? DEFAULT_RADIUS;
|
|
16
|
+
// Apply initial position
|
|
17
|
+
if (config.position) {
|
|
18
|
+
this.pos(config.position.x, config.position.y);
|
|
19
|
+
}
|
|
20
|
+
// Apply styling only if explicitly provided in config
|
|
21
|
+
// If not provided, VMobject defaults apply (no stroke, no fill)
|
|
22
|
+
if (config.strokeColor !== undefined) {
|
|
23
|
+
this.strokeColor = config.strokeColor;
|
|
24
|
+
}
|
|
25
|
+
if (config.strokeWidth !== undefined) {
|
|
26
|
+
this.strokeWidth = config.strokeWidth;
|
|
27
|
+
}
|
|
28
|
+
if (config.fillColor !== undefined) {
|
|
29
|
+
this.fillColor = config.fillColor;
|
|
30
|
+
}
|
|
31
|
+
if (config.fillOpacity !== undefined) {
|
|
32
|
+
this.fillOpacity = config.fillOpacity;
|
|
33
|
+
}
|
|
34
|
+
this.generateCirclePath();
|
|
35
|
+
}
|
|
36
|
+
get radius() {
|
|
37
|
+
return this.nodeRadius;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Generates a circular BezierPath approximation using cubic Bezier curves.
|
|
41
|
+
* Uses the standard 4-point circle approximation with control point factor ~0.5523.
|
|
42
|
+
*/
|
|
43
|
+
generateCirclePath() {
|
|
44
|
+
const path = new BezierPath();
|
|
45
|
+
const r = this.nodeRadius;
|
|
46
|
+
const k = 0.5522847498; // Magic number for circle approximation
|
|
47
|
+
// Start at the right-most point
|
|
48
|
+
path.moveTo(new Vector2(r, 0));
|
|
49
|
+
// Top-right quadrant
|
|
50
|
+
path.cubicTo(new Vector2(r, r * k), new Vector2(r * k, r), new Vector2(0, r));
|
|
51
|
+
// Top-left quadrant
|
|
52
|
+
path.cubicTo(new Vector2(-r * k, r), new Vector2(-r, r * k), new Vector2(-r, 0));
|
|
53
|
+
// Bottom-left quadrant
|
|
54
|
+
path.cubicTo(new Vector2(-r, -r * k), new Vector2(-r * k, -r), new Vector2(0, -r));
|
|
55
|
+
// Bottom-right quadrant
|
|
56
|
+
path.cubicTo(new Vector2(r * k, -r), new Vector2(r, -r * k), new Vector2(r, 0));
|
|
57
|
+
path.closePath();
|
|
58
|
+
this.pathList = [path];
|
|
59
|
+
}
|
|
60
|
+
getCenter() {
|
|
61
|
+
return this.position;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Vector2 } from '../../../core/math/Vector2/Vector2';
|
|
2
|
+
import type { GraphNode } from '../GraphNode';
|
|
3
|
+
import type { LayoutConfig } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Arranges nodes in a circular layout.
|
|
6
|
+
* Nodes are evenly distributed around a circle of the specified radius.
|
|
7
|
+
*/
|
|
8
|
+
export declare function circularLayout(nodes: GraphNode[], config?: LayoutConfig): Map<string, Vector2>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Vector2 } from '../../../core/math/Vector2/Vector2';
|
|
2
|
+
const DEFAULT_RADIUS = 3;
|
|
3
|
+
/**
|
|
4
|
+
* Arranges nodes in a circular layout.
|
|
5
|
+
* Nodes are evenly distributed around a circle of the specified radius.
|
|
6
|
+
*/
|
|
7
|
+
export function circularLayout(nodes, config = {}) {
|
|
8
|
+
const positions = new Map();
|
|
9
|
+
const radius = config.radius ?? DEFAULT_RADIUS;
|
|
10
|
+
const count = nodes.length;
|
|
11
|
+
if (count === 0)
|
|
12
|
+
return positions;
|
|
13
|
+
for (let i = 0; i < count; i++) {
|
|
14
|
+
const angle = (2 * Math.PI * i) / count - Math.PI / 2;
|
|
15
|
+
const x = radius * Math.cos(angle);
|
|
16
|
+
const y = radius * Math.sin(angle);
|
|
17
|
+
const node = nodes[i];
|
|
18
|
+
if (node) {
|
|
19
|
+
positions.set(node.id, new Vector2(x, y));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return positions;
|
|
23
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Vector2 } from '../../../core/math/Vector2/Vector2';
|
|
2
|
+
import type { GraphNode } from '../GraphNode';
|
|
3
|
+
import type { GraphEdge } from '../GraphEdge';
|
|
4
|
+
import type { LayoutConfig } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* Applies force-directed layout using spring simulation.
|
|
7
|
+
* Nodes repel each other while edges act as springs.
|
|
8
|
+
*/
|
|
9
|
+
export declare function forceDirectedLayout(nodes: GraphNode[], edges: GraphEdge[], config?: LayoutConfig): Map<string, Vector2>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Vector2 } from '../../../core/math/Vector2/Vector2';
|
|
2
|
+
const DEFAULT_ITERATIONS = 50;
|
|
3
|
+
const DEFAULT_SPRING_LENGTH = 1.5;
|
|
4
|
+
const DEFAULT_REPULSION = 1.0;
|
|
5
|
+
const DEFAULT_ATTRACTION = 0.1;
|
|
6
|
+
const DEFAULT_DAMPING = 0.85;
|
|
7
|
+
const DEFAULT_MIN_DISTANCE = 0.01;
|
|
8
|
+
/**
|
|
9
|
+
* Applies force-directed layout using spring simulation.
|
|
10
|
+
* Nodes repel each other while edges act as springs.
|
|
11
|
+
*/
|
|
12
|
+
export function forceDirectedLayout(nodes, edges, config = {}) {
|
|
13
|
+
const positions = new Map();
|
|
14
|
+
const iterations = config.iterations ?? DEFAULT_ITERATIONS;
|
|
15
|
+
const springLength = config.springLength ?? DEFAULT_SPRING_LENGTH;
|
|
16
|
+
const repulsion = config.repulsion ?? DEFAULT_REPULSION;
|
|
17
|
+
const attraction = config.attraction ?? DEFAULT_ATTRACTION;
|
|
18
|
+
const damping = config.damping ?? DEFAULT_DAMPING;
|
|
19
|
+
const minDistance = config.minDistance ?? DEFAULT_MIN_DISTANCE;
|
|
20
|
+
if (nodes.length === 0)
|
|
21
|
+
return positions;
|
|
22
|
+
// Initialize node states with current positions or random
|
|
23
|
+
const states = new Map();
|
|
24
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
25
|
+
const node = nodes[i];
|
|
26
|
+
if (node) {
|
|
27
|
+
// Start with a circular distribution to avoid overlap
|
|
28
|
+
const angle = (2 * Math.PI * i) / nodes.length;
|
|
29
|
+
const initialPos = new Vector2(Math.cos(angle) * 2, Math.sin(angle) * 2);
|
|
30
|
+
states.set(node.id, {
|
|
31
|
+
position: initialPos,
|
|
32
|
+
velocity: Vector2.ZERO
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Build edge lookup
|
|
37
|
+
const edgeSet = new Set();
|
|
38
|
+
for (const edge of edges) {
|
|
39
|
+
edgeSet.add(`${edge.source}-${edge.target}`);
|
|
40
|
+
edgeSet.add(`${edge.target}-${edge.source}`);
|
|
41
|
+
}
|
|
42
|
+
// Run simulation
|
|
43
|
+
for (let iter = 0; iter < iterations; iter++) {
|
|
44
|
+
const forces = new Map();
|
|
45
|
+
// Initialize forces
|
|
46
|
+
for (const node of nodes) {
|
|
47
|
+
forces.set(node.id, Vector2.ZERO);
|
|
48
|
+
}
|
|
49
|
+
// Calculate repulsion forces between all node pairs
|
|
50
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
51
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
52
|
+
const nodeA = nodes[i];
|
|
53
|
+
const nodeB = nodes[j];
|
|
54
|
+
if (!nodeA || !nodeB)
|
|
55
|
+
continue;
|
|
56
|
+
const stateA = states.get(nodeA.id);
|
|
57
|
+
const stateB = states.get(nodeB.id);
|
|
58
|
+
if (!stateA || !stateB)
|
|
59
|
+
continue;
|
|
60
|
+
const delta = stateA.position.subtract(stateB.position);
|
|
61
|
+
const distance = Math.max(delta.length(), minDistance);
|
|
62
|
+
const force = delta.normalize().multiply(repulsion / (distance * distance));
|
|
63
|
+
const forceA = forces.get(nodeA.id) ?? Vector2.ZERO;
|
|
64
|
+
const forceB = forces.get(nodeB.id) ?? Vector2.ZERO;
|
|
65
|
+
forces.set(nodeA.id, forceA.add(force));
|
|
66
|
+
forces.set(nodeB.id, forceB.subtract(force));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Calculate attraction forces along edges
|
|
70
|
+
for (const edge of edges) {
|
|
71
|
+
const stateA = states.get(edge.source);
|
|
72
|
+
const stateB = states.get(edge.target);
|
|
73
|
+
if (!stateA || !stateB)
|
|
74
|
+
continue;
|
|
75
|
+
const delta = stateB.position.subtract(stateA.position);
|
|
76
|
+
const distance = delta.length();
|
|
77
|
+
const displacement = distance - springLength;
|
|
78
|
+
const force = delta.normalize().multiply(displacement * attraction);
|
|
79
|
+
const forceA = forces.get(edge.source) ?? Vector2.ZERO;
|
|
80
|
+
const forceB = forces.get(edge.target) ?? Vector2.ZERO;
|
|
81
|
+
forces.set(edge.source, forceA.add(force));
|
|
82
|
+
forces.set(edge.target, forceB.subtract(force));
|
|
83
|
+
}
|
|
84
|
+
// Apply forces and update positions
|
|
85
|
+
for (const node of nodes) {
|
|
86
|
+
const state = states.get(node.id);
|
|
87
|
+
const force = forces.get(node.id);
|
|
88
|
+
if (!state || !force)
|
|
89
|
+
continue;
|
|
90
|
+
state.velocity = state.velocity.add(force).multiply(damping);
|
|
91
|
+
state.position = state.position.add(state.velocity);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Collect final positions
|
|
95
|
+
for (const node of nodes) {
|
|
96
|
+
const state = states.get(node.id);
|
|
97
|
+
if (state) {
|
|
98
|
+
positions.set(node.id, state.position);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return positions;
|
|
102
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Vector2 } from '../../../core/math/Vector2/Vector2';
|
|
2
|
+
import type { GraphNode } from '../GraphNode';
|
|
3
|
+
import type { GraphEdge } from '../GraphEdge';
|
|
4
|
+
import type { LayoutConfig } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* Arranges nodes in a hierarchical tree layout.
|
|
7
|
+
* Assumes edges represent parent-child relationships.
|
|
8
|
+
*/
|
|
9
|
+
export declare function treeLayout(nodes: GraphNode[], edges: GraphEdge[], config?: LayoutConfig): Map<string, Vector2>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Vector2 } from '../../../core/math/Vector2/Vector2';
|
|
2
|
+
const DEFAULT_LEVEL_HEIGHT = 1.5;
|
|
3
|
+
const DEFAULT_SIBLING_SPACING = 1.0;
|
|
4
|
+
/**
|
|
5
|
+
* Builds a tree structure from nodes and edges.
|
|
6
|
+
* Assumes directed edges from parent to child.
|
|
7
|
+
*/
|
|
8
|
+
function buildTree(nodes, edges) {
|
|
9
|
+
if (nodes.length === 0)
|
|
10
|
+
return undefined;
|
|
11
|
+
const hasIncoming = new Set();
|
|
12
|
+
for (const edge of edges) {
|
|
13
|
+
hasIncoming.add(edge.target);
|
|
14
|
+
}
|
|
15
|
+
const nodeMap = new Map();
|
|
16
|
+
for (const node of nodes) {
|
|
17
|
+
nodeMap.set(node.id, node);
|
|
18
|
+
}
|
|
19
|
+
// Build adjacency list (parent -> children)
|
|
20
|
+
const childrenMap = new Map();
|
|
21
|
+
for (const edge of edges) {
|
|
22
|
+
const children = childrenMap.get(edge.source) ?? [];
|
|
23
|
+
children.push(edge.target);
|
|
24
|
+
childrenMap.set(edge.source, children);
|
|
25
|
+
}
|
|
26
|
+
let rootId;
|
|
27
|
+
for (const node of nodes) {
|
|
28
|
+
if (!hasIncoming.has(node.id)) {
|
|
29
|
+
rootId = node.id;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!rootId && nodes[0]) {
|
|
34
|
+
rootId = nodes[0].id;
|
|
35
|
+
}
|
|
36
|
+
if (!rootId)
|
|
37
|
+
return undefined;
|
|
38
|
+
// Build tree recursively
|
|
39
|
+
function buildNode(id, depth) {
|
|
40
|
+
const children = (childrenMap.get(id) ?? [])
|
|
41
|
+
.map(childId => buildNode(childId, depth + 1));
|
|
42
|
+
const width = children.length === 0
|
|
43
|
+
? 1
|
|
44
|
+
: children.reduce((sum, c) => sum + c.width, 0);
|
|
45
|
+
return { id, children, depth, x: 0, width };
|
|
46
|
+
}
|
|
47
|
+
return buildNode(rootId, 0);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Recursively assigns x positions to tree nodes.
|
|
51
|
+
*/
|
|
52
|
+
function assignPositions(node, leftBound, spacing) {
|
|
53
|
+
if (node.children.length === 0) {
|
|
54
|
+
node.x = leftBound + node.width * spacing / 2;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let currentLeft = leftBound;
|
|
58
|
+
for (const child of node.children) {
|
|
59
|
+
assignPositions(child, currentLeft, spacing);
|
|
60
|
+
currentLeft += child.width * spacing;
|
|
61
|
+
}
|
|
62
|
+
// Center parent over children
|
|
63
|
+
const firstChild = node.children[0];
|
|
64
|
+
const lastChild = node.children[node.children.length - 1];
|
|
65
|
+
if (firstChild && lastChild) {
|
|
66
|
+
node.x = (firstChild.x + lastChild.x) / 2;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Collects all positions from the tree.
|
|
71
|
+
*/
|
|
72
|
+
function collectPositions(node, levelHeight, positions) {
|
|
73
|
+
positions.set(node.id, new Vector2(node.x, node.depth * levelHeight));
|
|
74
|
+
for (const child of node.children) {
|
|
75
|
+
collectPositions(child, levelHeight, positions);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Arranges nodes in a hierarchical tree layout.
|
|
80
|
+
* Assumes edges represent parent-child relationships.
|
|
81
|
+
*/
|
|
82
|
+
export function treeLayout(nodes, edges, config = {}) {
|
|
83
|
+
const positions = new Map();
|
|
84
|
+
const levelHeight = config.levelHeight ?? DEFAULT_LEVEL_HEIGHT;
|
|
85
|
+
const siblingSpacing = config.siblingSpacing ?? DEFAULT_SIBLING_SPACING;
|
|
86
|
+
const tree = buildTree(nodes, edges);
|
|
87
|
+
if (!tree)
|
|
88
|
+
return positions;
|
|
89
|
+
assignPositions(tree, 0, siblingSpacing);
|
|
90
|
+
collectPositions(tree, levelHeight, positions);
|
|
91
|
+
const allX = Array.from(positions.values()).map(p => p.x);
|
|
92
|
+
const minX = Math.min(...allX);
|
|
93
|
+
const maxX = Math.max(...allX);
|
|
94
|
+
const centerOffset = (minX + maxX) / 2;
|
|
95
|
+
for (const [id, pos] of positions) {
|
|
96
|
+
positions.set(id, new Vector2(pos.x - centerOffset, pos.y));
|
|
97
|
+
}
|
|
98
|
+
return positions;
|
|
99
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Color } from '../../core/math/color/Color';
|
|
2
|
+
/** Unique identifier for graph nodes. */
|
|
3
|
+
export type GraphNodeId = string;
|
|
4
|
+
/** Configuration for creating a graph node. */
|
|
5
|
+
export interface NodeConfig {
|
|
6
|
+
position?: {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
};
|
|
10
|
+
radius?: number;
|
|
11
|
+
strokeColor?: Color;
|
|
12
|
+
strokeWidth?: number;
|
|
13
|
+
fillColor?: Color;
|
|
14
|
+
fillOpacity?: number;
|
|
15
|
+
}
|
|
16
|
+
/** Configuration for creating a graph edge. */
|
|
17
|
+
export interface EdgeConfig {
|
|
18
|
+
strokeColor?: Color;
|
|
19
|
+
strokeWidth?: number;
|
|
20
|
+
curved?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** Supported layout algorithms. */
|
|
23
|
+
export type LayoutType = 'force-directed' | 'tree' | 'circular';
|
|
24
|
+
/** Configuration for graph layout algorithms. */
|
|
25
|
+
export interface LayoutConfig {
|
|
26
|
+
radius?: number;
|
|
27
|
+
levelHeight?: number;
|
|
28
|
+
siblingSpacing?: number;
|
|
29
|
+
iterations?: number;
|
|
30
|
+
springLength?: number;
|
|
31
|
+
repulsion?: number;
|
|
32
|
+
attraction?: number;
|
|
33
|
+
damping?: number;
|
|
34
|
+
minDistance?: number;
|
|
35
|
+
}
|
|
File without changes
|