From 6a544512bbc60cb90e0b0c373a3565731f26a0c4 Mon Sep 17 00:00:00 2001 From: Max <m.giller@tu-bs.de> Date: Thu, 28 Apr 2022 16:23:26 +0200 Subject: [PATCH] Implemented proper parsing for graph data --- src/editor/js/{editor.js => editor.ts} | 18 ++--- src/editor/js/structures/graph/graph.ts | 72 ++++++++++++++++++- .../js/structures/graph/graphelement.ts | 4 +- src/editor/js/structures/graph/link.ts | 72 +++++++++++++++++-- src/editor/js/structures/graph/node.ts | 35 ++++++--- src/editor/js/structures/graph/nodetype.ts | 17 +++-- .../js/structures/helper/serializableitem.ts | 3 +- 7 files changed, 187 insertions(+), 34 deletions(-) rename src/editor/js/{editor.js => editor.ts} (87%) diff --git a/src/editor/js/editor.js b/src/editor/js/editor.ts similarity index 87% rename from src/editor/js/editor.js rename to src/editor/js/editor.ts index c45f59c..7ab52e0 100644 --- a/src/editor/js/editor.js +++ b/src/editor/js/editor.ts @@ -1,13 +1,13 @@ import { State } from "./state"; -import * as Graph from "./graph"; import { loadGraphJson } from "../../datasets"; import ForceGraph from "force-graph"; import * as Interactions from "./interactions"; import { setSpace, SPACE } from "../../config"; +import { Graph } from "./structures/graph/graph"; -export var state = undefined; -export var graph = undefined; -export var renderer; +export let state: any = undefined; +export let graph: Graph = undefined; +export let renderer: any; window.onload = function () { // Only execute, if corresponding dom is present @@ -23,7 +23,7 @@ window.onload = function () { loadSpace(SPACE); }; -export function loadSpace(spaceId) { +export function loadSpace(spaceId: string) { if (state !== undefined && spaceId === SPACE) { return; } @@ -31,14 +31,14 @@ export function loadSpace(spaceId) { return loadGraphJson(SPACE).then((graphConfig) => { state = new State(); - graph = new Graph.Graph(graphConfig); + graph = Graph.parse(graphConfig); load(); - graph.restartSimulation(); + // graph.restartSimulation(); }); } -function extractPositions(event) { +function extractPositions(event: any) { return { graph: renderer.screen2GraphCoords(event.layerX, event.layerY), window: { x: event.clientX, y: event.clientY }, @@ -53,7 +53,7 @@ function load() { .height(600) .width(width) .graphData(graph.data) - .nodeLabel(Graph.NODE_LABEL) + .nodeLabel("label") .linkColor((link) => state.linkColor(link)) .nodeColor((node) => state.nodeColor(node)) .onNodeClick((node) => state.onNodeClick(node)) diff --git a/src/editor/js/structures/graph/graph.ts b/src/editor/js/structures/graph/graph.ts index 7a68061..b7a5ea8 100644 --- a/src/editor/js/structures/graph/graph.ts +++ b/src/editor/js/structures/graph/graph.ts @@ -24,9 +24,23 @@ export class Graph extends ManagedData { super(data); this.onChangeCallbacks = []; + this.connectElementsToGraph(); this.prepareIds(data); } + /** + * 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. @@ -105,7 +119,11 @@ export class Graph extends ManagedData { } protected storableData(data: GraphData): any { - let clean: GraphData; + const clean: GraphData = { + nodes: [], + links: [], + types: [] + }; clean.links = data.links.map((link) => link.getCleanInstance()); clean.nodes = data.nodes.map((node) => node.getCleanInstance()); @@ -186,6 +204,10 @@ export class Graph extends ManagedData { return true; } + 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. @@ -227,4 +249,52 @@ export class Graph extends ManagedData { return true; } + + public static parse(raw: any): Graph { + const data: GraphData = { + nodes: [], + links: [], + types: [] + }; + + // 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); + } } diff --git a/src/editor/js/structures/graph/graphelement.ts b/src/editor/js/structures/graph/graphelement.ts index 4072b75..85d0c20 100644 --- a/src/editor/js/structures/graph/graphelement.ts +++ b/src/editor/js/structures/graph/graphelement.ts @@ -5,9 +5,9 @@ export class GraphElement extends SerializableItem { protected isNode: boolean; protected isLink: boolean; - protected graph: Graph; + public graph: Graph; - constructor(graph: Graph) { + constructor(graph: Graph = undefined) { super(); this.graph = graph; this.isNode = false; diff --git a/src/editor/js/structures/graph/link.ts b/src/editor/js/structures/graph/link.ts index bdfd33e..b0eac9b 100644 --- a/src/editor/js/structures/graph/link.ts +++ b/src/editor/js/structures/graph/link.ts @@ -1,7 +1,7 @@ -import {GraphElement} from "./graphelement"; -import {Graph} from "./graph"; -import {Node} from "./node"; -import {GLOBAL_PARAMS} from "../helper/serializableitem"; +import { GraphElement } from "./graphelement"; +import { Graph } from "./graph"; +import { Node } from "./node"; +import { GLOBAL_PARAMS } from "../helper/serializableitem"; const LINK_PARAMS = ["source", "target", ...GLOBAL_PARAMS]; const LINK_SIM_PARAMS = ["index"]; @@ -10,17 +10,66 @@ export class Link extends GraphElement { public source: Node; public target: Node; - constructor(graph: Graph) { + private _sourceId: number; + private _targetId: number; + + constructor(graph: Graph = undefined) { super(graph); this.isLink = true; } + /** + * Id of the source node. + * @returns Source id. + */ + public get sourceId(): number { + if (this.source == undefined) { + return this._sourceId; + } + + return this.source.id; + } + + /** + * Removes stored node object and just saves the id instead. + * @param value New source id value. + */ + public set sourceId(value: number) { + this._sourceId = value; + this.source = undefined; + } + + /** + * Id of the target node. + * @returns Target id. + */ + public get targetId(): number { + if (this.target == undefined) { + return this._targetId; + } + + return this.target.id; + } + + /** + * Removes stored node object and just saves the id instead. + * @param value New target id value. + */ + public set targetId(value: number) { + this._targetId = value; + this.target = undefined; + } + public delete() { return this.graph.deleteLink(this); } public add(graph: Graph = this.graph) { this.graph = graph; + if (this.graph == undefined) { + return false; + } + return this.graph.addLink(this); } @@ -40,7 +89,16 @@ export class Link extends GraphElement { public getCleanInstance(): any { return { ...this.serialize(), - ...this.serializeProperties(LINK_SIM_PARAMS) + ...this.serializeProperties(LINK_SIM_PARAMS), }; } -} \ No newline at end of file + + public static parse(raw: any): Link { + const link: Link = new Link(); + + link.sourceId = Number(raw.source); + link.targetId = Number(raw.target); + + return link; + } +} diff --git a/src/editor/js/structures/graph/node.ts b/src/editor/js/structures/graph/node.ts index 5976137..42db8a1 100644 --- a/src/editor/js/structures/graph/node.ts +++ b/src/editor/js/structures/graph/node.ts @@ -1,9 +1,9 @@ -import {Graph} from "./graph"; -import {GraphElement} from "./graphelement" -import {NodeType} from "./nodetype"; -import {SerializedURL} from "../helper/serializedurl"; -import {Link} from "./link"; -import {GLOBAL_PARAMS} from "../helper/serializableitem"; +import { Graph } from "./graph"; +import { GraphElement } from "./graphelement"; +import { NodeType } from "./nodetype"; +import { SerializedURL } from "../helper/serializedurl"; +import { Link } from "./link"; +import { GLOBAL_PARAMS } from "../helper/serializableitem"; const NODE_PARAMS = [ "label", @@ -13,7 +13,7 @@ const NODE_PARAMS = [ "video", "type", "banner", - ...GLOBAL_PARAMS + ...GLOBAL_PARAMS, ]; const NODE_SIM_PARAMS = ["index", "x", "y", "vx", "vy", "fx", "fy"]; // Based on https://github.com/d3/d3-force#simulation_nodes @@ -26,7 +26,7 @@ export class Node extends GraphElement { public video: SerializedURL; public references: SerializedURL[]; - constructor(graph: Graph) { + constructor(graph: Graph = undefined) { super(graph); this.isNode = true; } @@ -37,6 +37,10 @@ export class Node extends GraphElement { public add(graph: Graph = this.graph) { this.graph = graph; + if (this.graph == undefined) { + return false; + } + return this.graph.addNode(this); } @@ -83,7 +87,18 @@ export class Node extends GraphElement { public getCleanInstance(): any { return { ...this.serialize(), - ...this.serializeProperties(NODE_SIM_PARAMS) + ...this.serializeProperties(NODE_SIM_PARAMS), }; } -} \ No newline at end of file + + public static parse(raw: any): Node { + const node: Node = new Node(); + + node.id = raw.id; + node.label = raw.name; + node.description = raw.description; + node.type = NodeType.parse(raw.type); + + return node; + } +} diff --git a/src/editor/js/structures/graph/nodetype.ts b/src/editor/js/structures/graph/nodetype.ts index d2c56b4..2e7d958 100644 --- a/src/editor/js/structures/graph/nodetype.ts +++ b/src/editor/js/structures/graph/nodetype.ts @@ -1,4 +1,4 @@ -import {GLOBAL_PARAMS, SerializableItem} from "../helper/serializableitem"; +import { GLOBAL_PARAMS, SerializableItem } from "../helper/serializableitem"; import { Graph } from "./graph"; import { GraphElement } from "./graphelement"; @@ -14,15 +14,24 @@ export class NodeType extends GraphElement { public delete(): boolean { // TODO: Implement - throw new Error("Function \"delete()\" has not been implemented."); + throw new Error('Function "delete()" has not been implemented.'); } public add(graph: Graph = this.graph): boolean { // TODO: Implement - throw new Error("Function \"add(graph)\" has not been implemented."); + throw new Error('Function "add(graph)" has not been implemented.'); } public getCleanInstance(): any { return this.serialize(); } -} \ No newline at end of file + + public static parse(raw: any): NodeType { + const type: NodeType = new NodeType(); + + type.name = raw; + type.color = "#ff0000" + + return type; + } +} diff --git a/src/editor/js/structures/helper/serializableitem.ts b/src/editor/js/structures/helper/serializableitem.ts index 793400a..a5e2313 100644 --- a/src/editor/js/structures/helper/serializableitem.ts +++ b/src/editor/js/structures/helper/serializableitem.ts @@ -19,8 +19,9 @@ export class SerializableItem { /** * Creates the current object based on raw, serialized data. * @param raw The serialized data. + * @returns Parsed data in final form. Could be the finalisd object. */ - public parse(raw: any) { + public static parse(raw: any): any { throw new Error("Method 'parse()' must be implemented."); } -- GitLab