Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import { Link } from "../common/link";
import { NodeType } from "../common/nodetype";
import { Node, NodeData } from "../common/node";
import * as Common from "../common/graph";
import { History } from "../common/history";
import { GraphContent, SimGraphData } from "../common/graph";
export class DynamicGraph extends Common.Graph {
private history: History<SimGraphData>;
// Callbacks
public onChangeCallbacks: { (data: DynamicGraph): void }[];
constructor(data?: GraphContent) {
super(data);
this.onChangeCallbacks = [];
this.history = new History<SimGraphData>(this);
}
/**
* Calls all registered callbacks for the onChange event.
* @private
*/
private triggerOnChange() {
this.onChangeCallbacks.forEach((fn) => fn(this));
}
/**
* Triggers change event on data-redo.
*/
protected onRedo() {
if (this.history.hasRedoCheckpoints()) {
const checkpoint = this.history.redo();
this.fromSerializedObject(checkpoint.data);
this.triggerOnChange();
}
}
/**
* Triggers change event on data-undo.
*/
protected onUndo() {
if (this.history.hasUndoCheckpoints()) {
const checkpoint = this.history.undo();
this.fromSerializedObject(checkpoint.data);
this.triggerOnChange();
}
}
public createObjectGroup(name?: string, color?: string): NodeType {
if (name == undefined) {
name = "Unnamed";
}
if (color == undefined) {
color = "#000000";
}
const objectGroup = super.createObjectGroup(name, color);
this.triggerOnChange();
return objectGroup;
}
public createNode(data?: NodeData): Node {
if (data == undefined) {
data = {
id: 0,
name: "Undefined",
type: this.objectGroups[0].name, // TODO: Change to id
};
}
return super.createNode(data);
}
private delete(id: string | number, fn: (id: string | number) => boolean) {
if (fn(id)) {
this.triggerOnChange();
return true;
}
return false;
}
public deleteNodeType(id: string): boolean {
return this.delete(id, super.deleteNodeType);
}
public deleteNode(id: number): boolean {
return this.delete(id, super.deleteNode);
}
public deleteLink(id: number): boolean {
return this.delete(id, super.deleteNode);
}
getLink(
sourceId: number,
targetId: number,
directionSensitive = true
): Link {
return this.links.find((l) => {
if (l.sourceId === sourceId && l.targetId === targetId) {
return true;
}
// Check other direction if allowed
return (
!directionSensitive &&
l.sourceId === targetId &&
l.targetId === sourceId
);
});
}
public createLink(source: number, target: number): Link {
const link = this.getLink(source, target, false);
if (link !== undefined) {
return link; // Already exists in graph.
}
return super.createLink(source, target);
}
/**
* Goes over all nodes and finds the closest node based on distance, that is not the given reference node.
* @param referenceNode Reference node to get closest other node to.
* @returns Closest node and distance. Undefined, if no closest node can be found.
*/
public getClosestNeighbor(referenceNode: Node): {
node: Node;
distance: number;
} {
if (referenceNode == undefined || this.nodes.length < 2) {
return undefined;
}
// Iterate over all nodes, keep the one with the shortest distance
let closestDistance = Number.MAX_VALUE;
let closestNode: Node = undefined;
this.nodes.forEach((node) => {
if (node.equals(referenceNode)) {
return; // Don't compare to itself
}
const currentDistance = Math.hypot(
referenceNode.x - node.x,
referenceNode.y - node.y
);
if (closestDistance > currentDistance) {
closestDistance = currentDistance;
closestNode = node;
}
});
return { node: closestNode, distance: closestDistance };
}
}