Skip to content
Snippets Groups Projects
graph.ts 8.72 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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[];
    
    /**
     * Basic graph data structure.
     */
    
    export class Graph
        extends SerializableItem<GraphData, SimGraphData>
        implements GraphContent
    {
    
        public objectGroups: NodeType[];
        public nameToObjectGroup: Map<string, NodeType>;
        public initialized: boolean;
    
    
        private idToNode: Map<number, Node>;
    
        private idToLink: Map<number, Link>;
    
        /**
         * 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;
            }
    
            this.createDefaultObjectGroupIfNeeded();
    
            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.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);
    
                data.objectGroups.forEach((group) =>
                    this.createObjectGroup(group.name, group.color)
    
            this.createDefaultObjectGroupIfNeeded();
    
            data.nodes.forEach((node) => this.createNode(node));
            data.links.forEach((link) => this.createLink(link.source, link.target));
    
        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]
                );
    
        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);
    
        public node(id: number): Node {
    
            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);
    
        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);
    
        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) {
    
            nodeTypes: Map<string, boolean>,
    
            linkTypes?: Map<string, boolean>
    
            const nodes = this.nodes.filter((l) => nodeTypes.get(l.type.name));
    
            let links;
            if (linkTypes === undefined) {
                links = this.links;
            } else {
    
                links = this.links.filter((l) => linkTypes.get(l.type.name));
    
            // Filter links which are connected to an invisible node
            links = links.filter(
    
                (l) =>
                    nodeTypes.get(l.source.type.name) &&
                    nodeTypes.get(l.target.type.name)
    
            return new Graph({
                nodes: nodes,
                links: links,
                objectGroups: this.objectGroups,
            });