diff --git a/src/editor/js/components/editor.tsx b/src/editor/js/components/editor.tsx index bbc0cce7d09e8c54b9daf6ac07917105e6599185..cf7c59b60b2a5d011bafc1ca9901c4675224548a 100644 --- a/src/editor/js/components/editor.tsx +++ b/src/editor/js/components/editor.tsx @@ -21,8 +21,14 @@ type clickPosition = { graph: { x: number; y: number }; window: { x: number; y: number }; }; +type positionTranslate = { + x: number; + y: number; + z: number; +}; export class Editor extends React.PureComponent<propTypes, stateTypes> { + private maxDistanceToConnect = 15; private defaultWarmupTicks = 100; private warmupTicks = 100; private renderer: any; @@ -31,6 +37,7 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { super(props); this.loadGraph = this.loadGraph.bind(this); this.loadSpace = this.loadSpace.bind(this); + this.extractPositions = this.extractPositions.bind(this); this.handleNodeClick = this.handleNodeClick.bind(this); this.onHistoryChange = this.onHistoryChange.bind(this); this.handleEngineStop = this.handleEngineStop.bind(this); @@ -41,7 +48,8 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { this.handleNodeCanvasObject = this.handleNodeCanvasObject.bind(this); this.handleLinkCanvasObject = this.handleLinkCanvasObject.bind(this); this.handleBackgroundClick = this.handleBackgroundClick.bind(this); - this.extractPositions = this.extractPositions.bind(this); + this.handleNodeDrag = this.handleNodeDrag.bind(this); + this.handleNodeDragEnd = this.handleNodeDragEnd.bind(this); this.renderer = React.createRef(); @@ -308,6 +316,32 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { return undefined; } + private handleNodeDrag(node: Node, translate: positionTranslate) { + this.setState({ + selectedNode: node, + }); + + const closest = this.state.graph.getClosestOtherNode(node); + + // Is close enough for new link? + if (closest.distance > this.maxDistanceToConnect) { + return; + } + + // Does link already exist? + if (node.neighbors.includes(closest.node)) { + return; + } + + // Add link + node.connect(closest.node); + this.forceUpdate(); + } + + private handleNodeDragEnd(node: Node, translate: positionTranslate) { + return; + } + private handleEngineStop() { // Only do something on first stop for each graph if (this.warmupTicks <= 0) { @@ -355,6 +389,8 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { nodeCanvasObjectMode={"after"} linkCanvasObject={this.handleLinkCanvasObject} linkCanvasObjectMode={"replace"} + onNodeDrag={this.handleNodeDrag} + onNodeDragEnd={this.handleNodeDragEnd} onBackgroundClick={(event) => this.handleBackgroundClick( event, diff --git a/src/editor/js/structures/graph/graph.ts b/src/editor/js/structures/graph/graph.ts index 0a562e0190c1cc871f2143347eef4f5db537c030..b8dc963ac3f09a1074d2acc923e393574ccaab57 100644 --- a/src/editor/js/structures/graph/graph.ts +++ b/src/editor/js/structures/graph/graph.ts @@ -287,6 +287,57 @@ export class Graph extends ManagedData { return true; } + /** + * Calculates the pythagoras distance. + * @param nodeA One node. + * @param nodeB The other node. + * @returns Distance between both nodes. + */ + private nodeDistance(nodeA: Node, nodeB: Node): number { + const a = nodeA as any; + const b = nodeB as any; + + const xDistance = Math.abs(a.x - b.x); + const yDistance = Math.abs(a.y - b.y); + + return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2)); + } + + /** + * 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 getClosestOtherNode(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 = undefined; + let closestNode: Node = undefined; + this.nodes.forEach((node) => { + if (node.equals(referenceNode)) { + return; // Don't compare to itself + } + + const currentDistance = this.nodeDistance(node, referenceNode); + + if ( + closestDistance == undefined || + closestDistance > currentDistance + ) { + closestDistance = currentDistance; + closestNode = node; + } + }); + + return { node: closestNode, distance: closestDistance }; + } + public static parse(raw: any): Graph { return new Graph(this.parseData(raw)); } diff --git a/src/editor/js/structures/graph/node.ts b/src/editor/js/structures/graph/node.ts index fcae0f0cd260a95e9eccd7772c888e0cef2ca6d9..426feecfe6974e90ed9e11fe0bf86c54a67371ac 100644 --- a/src/editor/js/structures/graph/node.ts +++ b/src/editor/js/structures/graph/node.ts @@ -85,6 +85,35 @@ export class Node extends GraphElement { return links; } + /** + * Calculates a list of all connected nodes to the current node. + * @returns Array containing all connected nodes. + */ + public get neighbors(): Node[] { + const nodes: Node[] = []; + + this.links.forEach((link) => { + // Find "other" node + let otherNode = link.source; + if (this.equals(otherNode)) { + otherNode = link.target; + } + + // Still undefined? + if (otherNode == undefined) { + // Link apparently not properly set up + return; + } + + // Add to list if doesn't exist + if (!nodes.includes(otherNode)) { + nodes.push(otherNode); + } + }); + + return nodes; + } + /** * Connects a given node to itself. Only works if they are in the same graph. * @param node Other node to connect.