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"]; type GraphData = { nodes: Node[]; links: Link[]; types: NodeType[] }; export class Graph extends ManagedData { public data: GraphData; private nextNodeId = 0; private nextLinkId = 0; private nextTypeId = 0; // Callbacks public onChangeCallbacks: { (data: any): void }[]; constructor(data: GraphData) { super(data); this.onChangeCallbacks = []; this.prepareIds(data); } /** * 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 { let highest = 0; 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 { let clean: GraphData; 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. */ private serializeData(data: GraphData): any { 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) { if (this.data.nodes.includes(node)) { return true; // Already exists in graph. } // 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 { if (!this.data.nodes.includes(node)) { return true; // Doesn't even exist in graph to begin with. } this.data.nodes.filter((n: Node) => n !== node); 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" ); return true; } /** * Adds a pre-created link to the graph. * @param link New link object. * @returns True, if successful. */ public addLink(link: Link): boolean { if (this.data.links.includes(link)) { return true; // Already exists in graph. } // Updateid 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 { if (!this.data.links.includes(link)) { return true; // Doesn't even exist in graph to begin with. } this.data.links.filter((l: Link) => l !== link); this.triggerOnChange(); // TODO: Use toString implementation of link this.storeCurrentData("Deleted link [" + link + "]"); return true; } }