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.createDefaultObjectGroupIfNeeded();

Matthias Konitzny
committed
this.objectGroups.forEach((group) =>
this.nameToObjectGroup.set(group.name, group)
);
this.nodes.forEach((node) => {
this.idToNode.set(node.id, node);
});
this.links.forEach((link) => {
this.idToLink.set(link.id, link);
this.connectElementsToGraph();
protected reset() {
this.nodes = [];
this.links = [];
this.nameToObjectGroup = new Map<string, NodeType>();
this.idToNode = new Map<number, Node>();
this.idToLink = new Map<number, Link>();
}
/**
* Sets the correct graph object for all the graph elements in data.
*/
connectElementsToGraph() {
this.nodes.forEach((n) => (n.graph = this));
this.links.forEach((l) => {
l.graph = this;
});
this.objectGroups.forEach((t) => (t.graph = this));
}
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 {
if (data.objectGroups === undefined) {
this.createObjectGroupsFromStrings(data.nodes);
} else {
data.objectGroups.forEach((group) =>
this.createObjectGroup(group.name, group.color)
this.createDefaultObjectGroupIfNeeded();

Matthias Konitzny
committed
data.nodes.forEach((node) => this.createNode(node));
data.links.forEach((link) => this.createLink(link.source, link.target));
this.updateNodeData();
return this;
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++) {
this.createObjectGroup(
nodeTypes[i],
Config.COLOR_PALETTE[i % Config.COLOR_PALETTE.length]
);
}
return objectGroups;
}
private createDefaultObjectGroupIfNeeded() {
if (this.objectGroups.length == 0) {
this.createObjectGroup("Default", "#000000");
}
}
/**
* 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 addNode(node: Node) {
node.id = this.nodes.length;
this.nodes.push(node);
this.idToNode.set(node.id, node);
}
public createNode(data?: NodeData): Node {
const node = new Node(this);
node.fromSerializedObject(data);
node.type = this.nameToObjectGroup.get(data.type);
node.neighbors = [];
node.links = [];
this.addNode(node);
return node;
}
public createLink(source: number, target: number): Link {
if (source === target) {
console.warn(
"Attempting to create a link where source equals target"
);
return;
}
const sourceNode = this.idToNode.get(source);
const targetNode = this.idToNode.get(target);
if (sourceNode === undefined || targetNode === undefined) {
console.warn("Tried to create a link between nonexisting nodes!");
return;
}
const link = new Link(sourceNode, targetNode, this);
sourceNode.links.push(link);
targetNode.links.push(link);
this.addLink(link);
return link;
}
public createObjectGroup(name?: string, color?: string): NodeType {
const group = new NodeType(name, color, this);
this.addObjectGroup(group);
return group;
}
private addObjectGroup(group: NodeType) {
group.id = this.objectGroups.length;
this.objectGroups.push(group);
this.nameToObjectGroup.set(group.name, group); // TODO: Replace with id
}
private addLink(link: Link) {
link.id = this.links.length;
this.links.push(link);
this.idToLink.set(link.id, link);
}
public deleteLink(id: number): boolean {
// Remove link from node data structures
const link = this.idToLink.get(id);
link.source.links.filter((l) => l.id != id);
link.target.links.filter((l) => l.id != id);
// Remove link from graph data structures
this.links = this.links.filter((l: Link) => l.id != id);
this.idToLink.delete(id);
return true;
}
public deleteNode(id: number): boolean {
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);
return true;
}
public deleteNodeType(id: string): boolean {
if (this.objectGroups.length <= 1) {
// Do not allow to delete the last node type.
return false;
}
// 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 = this.objectGroups[0];
}
return true;
}
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,
});