Newer
Older
import ManagedData from "../manageddata";
import { Link } from "./link";
import { NodeType } from "./nodetype";
import { Node } from "./node";
import { GLOBAL_PARAMS } from "../helper/serializableitem";
import { GraphElement } from "./graphelement";
const GRAPH_PARAMS = [...GLOBAL_PARAMS];
const GRAPH_DATA_PARAMS = ["nodes", "links", "types"];
export type GraphData = { nodes: Node[]; links: Link[]; types: NodeType[] };
export class Graph extends ManagedData {
private nextNodeId = 0;
private nextLinkId = 0;
private nextTypeId = 0;
public onChangeCallbacks: { (data: any): void }[];
super(data);
this.onChangeCallbacks = [];
this.connectElementsToGraph();
/**
* Sets the correct graph object for all the graph elements in data.
*/
connectElementsToGraph() {
this.data.nodes.forEach((n) => (n.graph = this));
this.data.links.forEach((l) => {
l.graph = this;
l.source = this.getNode(l.sourceId);
l.target = this.getNode(l.targetId);
});
this.data.types.forEach((t) => (t.graph = this));
}
/**
* Intuitive getter for links.
* @returns All links associated with the graph.
*/
public get links(): Link[] {
return this.data.links;
}
/**
* Intuitive getter for nodes.
* @returns All nodes associated with the graph.number
*/
public get nodes(): Node[] {
return this.data.nodes;
}
/**
* Intuitive getter for node types.
* @returns All node types associated with the graph.
*/
public get types(): NodeType[] {
return this.data.types;
}
/**
* Determines the highest, used ids for GraphElements in data for later use.
* @param data Data to analyse.
*/
private prepareIds(data: GraphData) {
if (data.links.length > 0) {
this.nextLinkId = this.getHighestId(data.links) + 1;
}
if (data.nodes.length > 0) {
this.nextNodeId = this.getHighestId(data.nodes) + 1;
}
if (data.types.length > 0) {
this.nextTypeId = this.getHighestId(data.types) + 1;
}
}
/**
* Finds the highest id from a list of graph elements.
* @param elements List of elements containing element with highest id.
* @returns Highest id in list.
*/
private getHighestId(elements: GraphElement[]): number {
elements.forEach((element) => {
if (highest < element.id) {
highest = element.id;
}
});
return highest;
}
/**
* Calls all registered callbacks for the onChange event.
* @private
*/
private triggerOnChange() {
this.onChangeCallbacks.forEach((fn) => fn(this.data));
}
/**
* Triggers change event on data-redo.
*/
protected onRedo() {
this.triggerOnChange();
}
/**
* Triggers change event on data-undo.
*/
protected onUndo() {
this.triggerOnChange();
}
protected storableData(data: GraphData): any {
const clean: GraphData = {
nodes: [],
links: [],
clean.links = data.links.map((link) => link.getCleanInstance());
clean.nodes = data.nodes.map((node) => node.getCleanInstance());
clean.types = data.types.map((type) => type.getCleanInstance());
return clean;
}
serialize(): any {
return this.serializeData(this.data);
}
/**
* Takes a data object and serializes it.
* @param data GraphData object to serialize.
* @returns Serialized data.
*/
return {
...this.serializeProperties(GRAPH_PARAMS),
...this.serializeProperties(GRAPH_DATA_PARAMS, data),
}
/**
* Adds a pre-created node to the graph.
* @param node New node object.
* @returns True, if successful.
*/
public addNode(node: Node) {
// Update id
node.id = this.nextNodeId;
this.nextNodeId += 1;
this.data.nodes.push(node);
this.triggerOnChange();
// TODO: Use toString implementation of node
this.storeCurrentData("Added node [" + node + "]");
return true;
}
/**
* Deletes a node from the graph.
* @param node Node object to remove.
* @returns True, if successful.
*/
public deleteNode(node: Node): boolean {
return true; // Doesn't even exist in graph to begin with.
this.data.nodes = this.data.nodes.filter((n: Node) => n.id !== node.id);
try {
// No save points should be created when deleting the links
this.disableStoring();
// Delete all the links that contain this node
node.links.forEach((l) => {
l.delete();
});
} finally {
this.enableStoring();
}
this.triggerOnChange();
// TODO: Use toString implementation of node
this.storeCurrentData(
"Deleted node [" + node + "] and all connected links"
);
getNode(id: number): Node {
return this.nodes.find((n) => n.id === id);
}
/**
* Adds a pre-created link to the graph.
* @param link New link object.
* @returns True, if successful.
*/
public addLink(link: Link): boolean {
link.id = this.nextLinkId;
this.nextLinkId += 1;
this.data.links.push(link);
this.triggerOnChange();
// TODO: Use toString implementation of link
this.storeCurrentData("Added link [" + link + "]");
return true;
}
/**
* Deletes a link from the graph.
* @param link Link object to remove.
* @returns True, if successful.
*/
public deleteLink(link: Link): boolean {
return true; // Doesn't even exist in graph to begin with.
this.data.links = this.data.links.filter(
(l: Link) =>
l.sourceId !== link.sourceId || l.targetId !== link.targetId
);
this.triggerOnChange();
// TODO: Use toString implementation of link
this.storeCurrentData("Deleted link [" + link + "]");
return true;
}
public static parse(raw: any): Graph {
const data: GraphData = {
nodes: [],
links: [],
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
};
// Parse nodes
if (raw.nodes === undefined) {
throw new Error(
"Invalid graph data format. Could not find any nodes."
);
}
raw.nodes.forEach((rawNode: any) => {
data.nodes.push(Node.parse(rawNode));
});
// Parse links
if (raw.links === undefined) {
throw new Error(
"Invalid graph data format. Could not find any links."
);
}
raw.links.forEach((rawLink: any) => {
data.links.push(Link.parse(rawLink));
// No need to replace node ids with proper node objects, since that should be done in the graph itself. Only have to prepare valid GraphData
});
// Collect all node types
// TODO: Remove, when types are directly parsed and not just implicit
data.nodes.forEach((node) => {
const sharedType: NodeType = data.types.find(
// TODO: Use id instead, but not defined at the moment
(type) => type.name === node.type.name
);
if (sharedType !== undefined) {
node.type = sharedType; // Assign it the stored type, to make sure that it has the same reference as every other node to this type
return;
}
// Doesn't exist in list yet, so add
data.types.push(node.type);
});
return new Graph(data);
}