Skip to content
Snippets Groups Projects
graph.ts 4.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • import { Link } from "../common/graph/link";
    import { NodeType } from "../common/graph/nodetype";
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
    import { Node, NodeData, SimNodeData } from "../common/graph/node";
    
    import * as Common from "../common/graph/graph";
    
    import { History } from "../common/history";
    
    import { GraphContent, SimGraphData } from "../common/graph/graph";
    
    
    export class DynamicGraph extends Common.Graph {
    
        public history: History<SimGraphData>;
    
    
        // Callbacks
        public onChangeCallbacks: { (data: DynamicGraph): void }[];
    
        constructor(data?: GraphContent) {
            super(data);
            this.onChangeCallbacks = [];
    
            this.history = new History<SimGraphData>(
                this,
                20,
                "Created new graph."
            );
    
        }
    
        /**
         * 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;
        }
    
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
        public createNode(
            data?: NodeData | SimNodeData,
            x?: number,
            y?: number,
            vx?: number,
            vy?: number
        ): Node {
    
            if (data == undefined) {
                data = {
                    id: 0,
                    name: "Undefined",
                    type: this.objectGroups[0].name, // TODO: Change to id
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
                    x: x,
                    y: y,
                    vx: vx,
                    vy: vy,
    
                };
            }
            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);
        }
    
        /**
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
         * Goes over all nodes and finds the closest node based on distance.
    
         * @returns Closest node and distance. Undefined, if no closest node can be found.
         */
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
        public getClosestNode(
            x: number,
            y: number,
            exclude?: Node
        ): {
    
            node: Node;
            distance: number;
        } {
            // Iterate over all nodes, keep the one with the shortest distance
            let closestDistance = Number.MAX_VALUE;
            let closestNode: Node = undefined;
            this.nodes.forEach((node) => {
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
                if (node.equals(exclude)) {
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
                const currentDistance = Math.hypot(x - node.x, y - node.y);
    
    
                if (closestDistance > currentDistance) {
                    closestDistance = currentDistance;
                    closestNode = node;
                }
            });
    
            return { node: closestNode, distance: closestDistance };
        }
    }