diff --git a/installation.sh b/installation.sh index 718b9693..0cc3830b 100755 --- a/installation.sh +++ b/installation.sh @@ -31,11 +31,6 @@ function parse() { parse "$@" echo "=== Install / Update script for PsyNeuLinkViewer and meta-diagram ===" -#echo "Install is" -#echo $INSTALL -#echo "Update is" -#echo $UPDATE - if [ "$INSTALL" = true ]; then diff --git a/src/components/Main.js b/src/components/Main.js index 98bac309..f8975673 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -7,8 +7,10 @@ import Composition from './views/compositions/Composition'; import GenericMechanism from './views/mechanisms/GenericMechanism'; import MetaDiagram, { CallbackTypes, ComponentsMap, EventTypes, Position } from "@metacell/meta-diagram"; import CustomLinkWidget from './views/projections/CustomLinkWidget'; +import { generateMetaGraph } from '../model/utils'; const mockModel = require('../resources/model').mockModel; + const styles = () => ({ root: { height: 'calc(100vh - 3.5rem)', @@ -40,23 +42,22 @@ class Main extends React.Component { this.handlePreUpdates = this.handlePreUpdates.bind(this); this.handlePostUpdates = this.handlePostUpdates.bind(this); this.mouseMoveCallback = this.mouseMoveCallback.bind(this); - } - calculateDelta(metaNode, metaNodeModel) { - let oldPosition = new Position(metaNode.position.x, metaNode.position.y); - let newPosition = new Position(metaNodeModel.position.x, metaNodeModel.position.y); - return oldPosition.sub(newPosition) + this.metaGraph = generateMetaGraph([...this.metaModel[PNLClasses.COMPOSITION], ...this.metaModel[PNLClasses.MECHANISM]]); + this.metaGraph.addLinks(this.metaModel[PNLClasses.PROJECTION]); } handlePostUpdates(event) { switch(event.function) { case CallbackTypes.POSITION_CHANGED: { - this.interpreter.updateModel(event.entity); - break; + const node = event.entity; + this.metaGraph.handleNodePositionChanged(node, this.mousePos.x, this.mousePos.y); + this.interpreter.updateModel(node); + return true; } default: { console.log('Function callback type not yet implemented ' + event.function); - break; + return false; } } } @@ -94,8 +95,8 @@ class Main extends React.Component { ; + + constructor(metaNodeModel: MetaNodeModel) { + this.node = metaNodeModel; + this.children = new Map() + } + + getID() : string{ + return this.node.getID() + } + + getNode() : MetaNodeModel{ + return this.node + } + + getChild(id:string) { + return this.children.get(id) + } + + addChild(graph: Graph) : void { + this.children.set(graph.getID(), graph) + } + + getChildren(): MetaNodeModel[] { + return Array.from(this.children.values()).map(g => g.getNode()) + } + + getDescendancy(): MetaNodeModel[] { + const descendancy = this.getChildren() + for(const graph of Array.from(this.children.values())){ + descendancy.push(...graph.getDescendancy()) + } + return descendancy + } + + dfs(id: string): MetaNodeModel | boolean { + if(this.getID() === id){ + return this.node + } + for (let node of Array.from(this.children.values())) { + const found = node.dfs(id) + if(found){ + return found + } + } + return false + } + + getContainerBoundingBox() : any { + return this.node.getNodeBoundingBox(); + } +} + + +export class MetaGraph { + private readonly roots: Map; + private readonly links: MetaLinkModel[]; + + constructor() { + this.roots = new Map() + this.links = []; + } + + addLinks(links: MetaLink[]) { + links.forEach( (child: MetaLink) => { + const link = child.toModel(); + const source = this.getNodeDFS(child.getSourceId()); + const target = this.getNodeDFS(child.getTargetId()); + if (source && target) { + link.setSourcePort(source.getPort(child.getSourcePortId())); + link.setTargetPort(target.getPort(child.getTargetPortId())); + this.links.push(link); + } + }); + } + + getLinks(): MetaLinkModel[] { + return this.links; + } + + addNode(metaNodeModel:MetaNodeModel): void { + const path = metaNodeModel.getGraphPath() + if(path.length === 1){ + this.roots.set(metaNodeModel.getID(), new Graph(metaNodeModel)) + }else{ + path.pop() // Removes own id from path + const parentGraph = this.findNodeGraph(path) + parentGraph.addChild(new Graph(metaNodeModel)) + } + } + + getNodes() : MetaNodeModel[] { + const nodes = [] + for(const graph of Array.from(this.roots.values())){ + nodes.push(graph.getNode()) + nodes.push(...graph.getDescendancy()) + } + return nodes + } + + getAncestors(node : MetaNodeModel): MetaNodeModel[] { + const path = node.getGraphPath() + const oldestAncestor = this.getRoot(path[0]) + return [oldestAncestor.getNode(), ...oldestAncestor.getChildren()] + } + + getRoot(rootId: string) : Graph{ + const root = this.roots.get(rootId) + if(root === undefined){ + throw new Error('unknown parent ' + rootId); + } + return root + } + + getChildren(parent : MetaNodeModel): MetaNodeModel[] { + const path = parent.getGraphPath() + if (path.length === 1) { + const root = this.getRoot(parent.getID()) + return root.getChildren() + } else { + const graph = this.findNodeGraph(path) + return graph.getChildren() + } + } + + getParent(node : MetaNodeModel): MetaNodeModel | undefined { + const path = node.getGraphPath() + if (path.length === 1) { + return undefined + } else { + path.pop() // removes own id from path + const parentGraph = this.findNodeGraph(path) + return parentGraph.getNode() + } + } + + getNodeDFS(nodeId: string): MetaNodeModel | undefined { + for (let root of Array.from(this.roots.values())) { + const found = root.dfs(nodeId) + if(found){ + // @ts-ignore + return found + } + } + return undefined + } + + getNodeContainerBoundingBox(node: MetaNodeModel) : any { + const graph = this.findNodeGraph(node.getGraphPath()) + return graph.getContainerBoundingBox() + } + + private findNodeGraph(path: string[]) : Graph { + const rootId = path.shift() + // @ts-ignore + let parent = this.getRoot(rootId) + while(path.length > 0){ + const next = path.shift() + // @ts-ignore + parent = parent.getChild(next) + if (parent === undefined){ + throw new Error('unknown parent ' + rootId); + } + } + return parent + } + + handleNodePositionChanged(metaNodeModel: MetaNodeModel, cursorX: number, cursorY: number){ + // TODO: Update node parent (add or remove parent) + // update node graph path, + // bounding boxes of parents + + // Update children position (children should move the same delta as node) + this.updateChildrenPosition(metaNodeModel) + // Update local position / relative position to the parent + this.updateNodeLocalPosition(metaNodeModel) + // update the graph for right parent children relationship + this.updateGraph(metaNodeModel, cursorX, cursorY); + } + + updateGraph(metaNodeModel: MetaNodeModel, cursorX: number, cursorY: number) { + let parent = undefined; + let search = true; + this.roots.forEach((node, id) => { + if (node.getContainerBoundingBox().containsPoint(cursorX, cursorY)) { + parent = node; + } + }); + // TODO add the new child to the graph and update graphPath for the metaNodeModel instance + } + + private updateChildrenPosition(metaNodeModel: MetaNodeModel){ + const children = this.getChildren(metaNodeModel); + + children.forEach(n => { + /* + No need to explicitly call updateChildrenPosition for n children because it will happen automatically in + the event listener + */ + // @ts-ignore + const localPosition = n.getLocalPosition() + n.setPosition(metaNodeModel.getX() + localPosition.x, metaNodeModel.getY() + localPosition.y) + + }) + } + + private updateNodeLocalPosition(metaNodeModel: MetaNodeModel){ + const parent = this.getParent(metaNodeModel) + metaNodeModel.updateLocalPosition(parent) + } + + updateNodesContainerBoundingBoxes(nodes: MetaNodeModel[]): void { + nodes.forEach(n => n.setContainerBoundingBox(this.getNodeContainerBoundingBox(n))) + } +} diff --git a/src/components/views/compositions/Composition.js b/src/components/views/compositions/Composition.js index 40eb2cbe..73a36719 100644 --- a/src/components/views/compositions/Composition.js +++ b/src/components/views/compositions/Composition.js @@ -83,8 +83,6 @@ class Composition extends React.Component { expanded: false, width: props.model.options.width, height: props.model.options.height, - x: props.model.options.x, - y: props.model.options.y } this.changeVisibility = this.changeVisibility.bind(this); } @@ -101,12 +99,9 @@ class Composition extends React.Component { { - this.setState({ x: d.x, y: d.y }); - }} + position={{ x: this.props.model.options.x, y: this.props.model.options.y }} onResizeStop={(e, direction, ref, delta, position) => { - this.props.model.updateSize(ref.style.width, ref.style.height); + this.props.model.updateSize(parseFloat(ref.style.width), parseFloat(ref.style.height)); this.setState({ width: ref.style.width, height: ref.style.height, diff --git a/src/model/Interpreter.ts b/src/model/Interpreter.ts index 8ba2007c..20071671 100644 --- a/src/model/Interpreter.ts +++ b/src/model/Interpreter.ts @@ -101,9 +101,11 @@ export default class ModelInterpreter { } updateModel(item: MetaNode|MetaLink) { + // TODO: here we sync the MetaModel node with the MetaNodeModel, question is, do we need it? + // the MetaNodeModel has already serialization implemented and we don't need anything else + // from the metamodel once it's passed to meta-diagram, to investigate whether we need this sync + // or we can simply rely on the metaNodeModel to be serialised and passed to the backend. // if (this.metaModelMap[item.getShape()].has(item.getId())) { - console.log('this is where I update the node'); - console.log(item); // } } diff --git a/src/model/nodes/composition/CompositionNode.ts b/src/model/nodes/composition/CompositionNode.ts index e94a091b..555b346e 100644 --- a/src/model/nodes/composition/CompositionNode.ts +++ b/src/model/nodes/composition/CompositionNode.ts @@ -44,9 +44,10 @@ export default class CompositionNode extends MechanismNode { } if (this.extra?.boundingBox) { + console.log(this.extra.boundingBox); this.extra.position = { - x: this.extra.boundingBox.llx + 75, - y: this.extra.boundingBox.lly + 75 + x: this.extra.boundingBox.llx, + y: this.extra.boundingBox.lly } } diff --git a/src/model/utils.js b/src/model/utils.js index 26b25e07..de96b2b6 100644 --- a/src/model/utils.js +++ b/src/model/utils.js @@ -1,6 +1,6 @@ import { PNLClasses } from '../constants'; -// const html2json = require('html2json').html2json; - +import {MetaNode} from '@metacell/meta-diagram'; +import { MetaGraph } from '../components/graph/MetaGraph'; export function buildModel(frontendModel, coord, prevModel) { let finalModel = { @@ -18,8 +18,6 @@ export function buildModel(frontendModel, coord, prevModel) { y: 150, }; - let linkCounter = 1; - if (coord) { coordinates.x = coord.x; coordinates.y = coord.y; @@ -58,4 +56,17 @@ export function buildModel(frontendModel, coord, prevModel) { }); return finalModel; -} \ No newline at end of file +} + +export function generateMetaGraph(metaNodes) { + const metaGraph = new MetaGraph() + metaNodes.sort(function(a, b) { + return a.getDepth() - b.getDepth(); + }); + + for(const mn of metaNodes){ + const metaNodeModel = mn.toModel() + metaGraph.addNode(metaNodeModel) + } + return metaGraph +}