Newer
Older
import * as Config from "../config";
import { Node, NodeData, SimNodeData } from "./node";
import { Link, LinkData, SimLinkData } from "./link";
import { NodeType, NodeTypeData } from "./nodetype";
import { SerializableItem } from "./serializableitem";
export interface Coordinate {
x: number;
y: number;
z: number;
}
export interface GraphData {
nodes: NodeData[];
links: LinkData[];
objectGroups?: NodeTypeData[];
}
export interface SimGraphData {
nodes: SimNodeData[];
links: SimLinkData[];
objectGroups: NodeTypeData[];
}
export interface GraphContent {
nodes: Node[];
links: Link[];
objectGroups?: NodeType[];
/**
* Basic graph data structure.
*/
export class Graph
extends SerializableItem<GraphData, SimGraphData>
implements GraphContent
{

Matthias Konitzny
committed
public nodes: Node[];
public links: Link[];
public objectGroups: NodeType[];
public nameToObjectGroup: Map<string, NodeType>;
public initialized: boolean;
private idToLink: Map<number, Link>;

Matthias Konitzny
committed
/**
* Creates a new Graph object.
* Make sure the nodes and links are connected to the correct objects before calling this method!
*/
constructor(data?: GraphContent) {
super(0);
if (data === undefined) {
this.initialized = false;
return;
}

Matthias Konitzny
committed
Object.assign(this, data);
this.nameToObjectGroup = new Map<string, NodeType>();

Matthias Konitzny
committed
this.objectGroups.forEach((group) =>
this.nameToObjectGroup.set(group.name, group)
);
this.idToNode = new Map<number, Node>();
this.nodes.forEach((node) => {
this.idToNode.set(node.id, node);
});
this.idToLink = new Map<number, Link>();
this.links.forEach((link) => {
this.idToLink.set(link.id, link);
public toJSONSerializableObject(): GraphData {
return {
nodes: this.nodes.map((node) => node.toJSONSerializableObject()),
links: this.links.map((link) => link.toJSONSerializableObject()),
objectGroups: this.objectGroups.map((group) =>
group.toJSONSerializableObject()
),
};
public toHistorySerializableObject(): SimGraphData {
return {
nodes: this.nodes.map((node) => node.toHistorySerializableObject()),
links: this.links.map((link) => link.toHistorySerializableObject()),
objectGroups: this.objectGroups.map((group) =>
group.toHistorySerializableObject()
),
};
}
public fromSerializedObject(data: GraphData | SimGraphData): Graph {
let objectGroups: Array<NodeType>;
if (data.objectGroups === undefined) {
objectGroups = this.createObjectGroupsFromStrings(data.nodes);
} else {
objectGroups = this.createObjectGroupsFromObjects(
data.objectGroups
);

Matthias Konitzny
committed
this.nameToObjectGroup = new Map<string, NodeType>();
objectGroups.forEach((group) =>
this.nameToObjectGroup.set(group.name, group)
);
this.nodes = this.createNodes(data.nodes);

Matthias Konitzny
committed
this.nodes.forEach((node) => {
this.idToNode.set(node.id, node);
});
this.links = data.links.map((link, i) => {
const l = new Link();
l.id = i;
l.source = this.idToNode.get(link.source);
l.target = this.idToNode.get(link.target);
return l;
});
this.idToLink = new Map<number, Link>();
this.links.forEach((link) => {
this.idToLink.set(link.id, link);
});
this.updateNodeData();
return this;
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
private createNodes(nodeJSONData: NodeData[]): Array<Node> {
const nodes: Array<Node> = [];
for (const nodeData of nodeJSONData) {
const node = new Node();
node.fromSerializedObject(nodeData);
node.type = this.nameToObjectGroup.get(nodeData.type);
node.neighbors = [];
node.links = [];
nodes.push(node);
}
return nodes;
}
private createObjectGroupsFromStrings(nodes: NodeData[]): Array<NodeType> {
const objectGroups: NodeType[] = [];
const nodeClasses: string[] = [];
nodes.forEach((node) => nodeClasses.push(node.type));
const nodeTypes = [...new Set(nodeClasses)].map((c) => String(c));
for (let i = 0; i < nodeTypes.length; i++) {
const nodeType = new NodeType();
nodeType.fromSerializedObject({
id: i,
name: nodeTypes[i],
color: Config.COLOR_PALETTE[i % Config.COLOR_PALETTE.length],
});
}
return objectGroups;
}
private createObjectGroupsFromObjects(
groups: NodeTypeData[]
): Array<NodeType> {
return groups.map((group) => {
const t = new NodeType();
t.fromSerializedObject(group);
return t;
});

Matthias Konitzny
committed
}
/**
* Updates the graph data structure to contain additional values.
* Creates a 'neighbors' and 'links' array for each node object.
*/
private updateNodeData(): Link[] {
this.links.forEach((link) => {
const a = link.source;
const b = link.target;
a.neighbors.push(b);
b.neighbors.push(a);
a.links.push(link);
b.links.push(link);
});
return this.links;
}
public removeFloatingNodes() {
this.nodes = this.nodes.filter((node) => node.neighbors.length > 0);
return this.idToNode.get(id);
}
private checkNode(node: Node) {
for (const neighbor of node.neighbors) {
if (this.idToNode.get(neighbor.id) === undefined) {
return false;
}
}

Matthias Konitzny
committed
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
for (const link of node.links) {
if (this.idToLink.get(link.id) === undefined) {
return false;
}
}
return node.isInitialized();
}
private checkLink(link: Link) {
return (
link.isInitialized() &&
!(
this.idToNode.get(link.source.id) === undefined ||
this.idToNode.get(link.target.id) === undefined
)
);
}
public addNode(node: Node) {
node.id = this.nodes.length;
if (!this.checkNode(node)) {
return false;
}
this.nodes.push(node);
this.idToNode.set(node.id, node);
return true;
}
public addLink(link: Link) {
link.id = this.links.length;
if (!this.checkLink(link)) {
return false;
}
this.links.push(link);
this.idToLink.set(link.id, link);
return true;
}
public deleteLink(id: number) {
this.links = this.links.filter((l: Link) => l.id !== id);
this.idToLink.delete(id);
}
public deleteNode(id: number) {
const node = this.idToNode.get(id);
this.idToNode.delete(id);
for (const link of node.links) {
this.deleteLink(link.id);
}
this.nodes = this.nodes.filter((n: Node) => n.id !== id);
}
public deleteNodeType(id: string) {
// TODO: Change to id/number
const nodeType = this.nameToObjectGroup.get(id);
for (const node of this.nodes) {
if (node.type.id === nodeType.id) {
node.type = undefined;
}
}
}
public view(
linkTypes?: Map<string, boolean>

Matthias Konitzny
committed
// Filter nodes depending on type

Matthias Konitzny
committed
const nodes = this.nodes.filter((l) => nodeTypes.get(l.type.name));

Matthias Konitzny
committed
// Filter links depending on type
let links;
if (linkTypes === undefined) {
links = this.links;
} else {

Matthias Konitzny
committed
links = this.links.filter((l) => linkTypes.get(l.type.name));

Matthias Konitzny
committed
// Filter links which are connected to an invisible node
links = links.filter(

Matthias Konitzny
committed
(l) =>
nodeTypes.get(l.source.type.name) &&
nodeTypes.get(l.target.type.name)

Matthias Konitzny
committed
);
return new Graph({
nodes: nodes,
links: links,
objectGroups: this.objectGroups,
});