Skip to content
Snippets Groups Projects
graph.ts 4.44 KiB
Newer Older
  • Learn to ignore specific revisions
  • import { Link } from "../common/link";
    import { NodeType } from "../common/nodetype";
    import { Node, NodeData } from "../common/node";
    import * as Common from "../common/graph";
    import { History } from "../common/history";
    import { GraphContent, SimGraphData } from "../common/graph";
    
    export class DynamicGraph extends Common.Graph {
        private history: History<SimGraphData>;
    
        // Callbacks
        public onChangeCallbacks: { (data: DynamicGraph): void }[];
    
        constructor(data?: GraphContent) {
            super(data);
            this.onChangeCallbacks = [];
            this.history = new History<SimGraphData>(this);
        }
    
        /**
         * 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): Node {
            if (data == undefined) {
                data = {
                    id: 0,
                    name: "Undefined",
                    type: this.objectGroups[0].name, // TODO: Change to id
                };
            }
            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, that is not the given reference node.
         * @param referenceNode Reference node to get closest other node to.
         * @returns Closest node and distance. Undefined, if no closest node can be found.
         */
        public getClosestNeighbor(referenceNode: Node): {
            node: Node;
            distance: number;
        } {
            if (referenceNode == undefined || this.nodes.length < 2) {
                return undefined;
            }
    
            // Iterate over all nodes, keep the one with the shortest distance
            let closestDistance = Number.MAX_VALUE;
            let closestNode: Node = undefined;
            this.nodes.forEach((node) => {
                if (node.equals(referenceNode)) {
                    return; // Don't compare to itself
                }
    
                const currentDistance = Math.hypot(
                    referenceNode.x - node.x,
                    referenceNode.y - node.y
                );
    
                if (closestDistance > currentDistance) {
                    closestDistance = currentDistance;
                    closestNode = node;
                }
            });
    
            return { node: closestNode, distance: closestDistance };
        }
    }