Newer
Older
import { Link } from "../common/graph/link";
import { NodeType } from "../common/graph/nodetype";
import { Node, NodeData, SimNodeData } from "../common/graph/node";
import * as Common from "../common/graph/graph";
import { History } from "../common/history";
import { GraphContent, GraphData, SimGraphData } from "../common/graph/graph";
export class DynamicGraph extends Common.Graph {
public history: History<SimGraphData>;
// Callbacks
public onChangeCallbacks: { (data: DynamicGraph): void }[];
constructor(data?: GraphContent) {
super(data);
this.onChangeCallbacks = [];
if (data != undefined) {
this.history = new History<SimGraphData>(
this,
20,
"Created new graph."
);
}
}
public fromSerializedObject(data: GraphData | SimGraphData): DynamicGraph {
super.fromSerializedObject(data);
this.history = new History<SimGraphData>(
this,
20,
"Created new graph."
);
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
}
/**
* 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 | SimNodeData,
x?: number,
y?: number,
vx?: number,
vy?: number
): Node {
if (data == undefined) {
data = {
id: 0,
name: "Undefined",
type: this.objectGroups[0].name, // TODO: Change to id
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
};
}
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.
* @returns Closest node and distance. Undefined, if no closest node can be found.
*/
public getClosestNode(
x: number,
y: number,
exclude?: Node
): {
node: Node;
distance: number;
} {
// Iterate over all nodes, keep the one with the shortest distance
let closestDistance = Number.MAX_VALUE;
let closestNode: Node = undefined;
this.nodes.forEach((node) => {
return; // Don't compare to itself
}
const currentDistance = Math.hypot(x - node.x, y - node.y);
if (closestDistance > currentDistance) {
closestDistance = currentDistance;
closestNode = node;
}
});
return { node: closestNode, distance: closestDistance };
}
}