From 2d2d5de259e00cb7cdfee37dc794ac6afd4bcda0 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Mon, 5 Sep 2022 21:19:59 +0100 Subject: [PATCH 01/14] #36 composition implementation, metanode instantiation and pluggin required parts of the library --- src/components/Main.js | 18 +-- .../views/compositions/Composition.js | 123 ++++++++++++++++++ .../views/mechanisms/GenericMechanism.js | 32 +---- src/model/Interpreter.ts | 34 +++-- .../nodes/composition/CompositionNode.ts | 75 +++++++---- src/model/nodes/mechanism/MechanismNode.ts | 18 ++- src/model/utils.js | 6 +- 7 files changed, 231 insertions(+), 75 deletions(-) create mode 100644 src/components/views/compositions/Composition.js diff --git a/src/components/Main.js b/src/components/Main.js index d437f503..d079ab17 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -1,13 +1,14 @@ import React from 'react'; // import MechanismNode from '../model/nodes/mechanism/MechanismNode'; import { withStyles } from '@mui/styles'; +import { PNLClasses } from '../constants'; +import { buildModel } from '../model/utils'; import BG from "../assets/svg/bg-dotted.svg"; import ModelInterpreter from '../model/Interpreter'; +import Composition from './views/compositions/Composition'; +import GenericMechanism from './views/mechanisms/GenericMechanism'; import MetaDiagram, { ComponentsMap } from "@metacell/meta-diagram"; import CustomLinkWidget from './views/projections/CustomLinkWidget'; -import GenericMechanism from './views/mechanisms/GenericMechanism'; -import { buildModel } from '../model/utils'; -import { PNLClasses } from '../constants'; const mockModel = require('../resources/model').mockModel; @@ -33,14 +34,15 @@ class Main extends React.Component { const model = interpreter.getModel(); const metaModel = buildModel(model); - const componentsMap = new ComponentsMap( - new Map(Object.entries({'mechanism': GenericMechanism})), - new Map(Object.entries({'projection': CustomLinkWidget})) - ) + const componentsMap = new ComponentsMap(new Map(), new Map()); + + componentsMap.nodes.set(PNLClasses.COMPOSITION, Composition); + componentsMap.nodes.set(PNLClasses.MECHANISM, GenericMechanism); + componentsMap.links.set(PNLClasses.PROJECTION, CustomLinkWidget); return (
- ({ + root: { + '& .react-draggable': { + background: draggableBg, + border: `0.125rem solid ${chipBorderColor}`, + borderRadius: '0.75rem', + display: "flex !important", + alignItems: "center", + justifyContent: "center", + + '&:hover': { + borderColor: listItemActiveBg + }, + }, + + '& .MuiChip-root': { + background: chipBorderColor, + borderRadius: '0.75rem', + padding: '0 0.5rem', + display: "flex", + left: 0, + position: 'absolute', + color: chipTextColor, + top: '-1.75rem', + alignItems: "center", + height: '1.5rem', + letterSpacing: '-0.005rem', + fontWeight: 510, + fontSize: '0.8125rem', + lineHeight: '1.25rem', + flexDirection: 'row-reverse', + + '& .MuiChip-label': { + padding: 0, + }, + + '& .MuiChip-icon': { + margin: '0 0 0 0.25rem', + }, + }, + }, + + selected: { + '&:before': { + left: 0, + ...commonStyles + }, + + '&:after': { + right: 0, + ...commonStyles + }, + + '& .MuiChip-root': { + background: listItemActiveBg + }, + + '& .react-draggable': { + borderColor: listItemActiveBg, + } + }, +}); + +class Composition extends React.Component { + constructor(props) { + super(props); + this.state = { + expanded: false, + width: 442, + height: 192, + x: 0, + y: 0 + } + this.changeVisibility = this.changeVisibility.bind(this); + } + + changeVisibility() { + this.setState({expanded: !this.state.expanded}); + } + + render() { + const { expanded } = this.state; + const { classes } = this.props; + + return ( + + { + this.setState({ x: d.x, y: d.y }); + }} + onResizeStop={(e, direction, ref, delta, position) => { + this.setState({ + width: ref.style.width, + height: ref.style.height, + ...position + }); + }} + > + } label="New Comp" color="secondary" /> + + + ); + } +} + +export default withStyles(styles)(Composition); diff --git a/src/components/views/mechanisms/GenericMechanism.js b/src/components/views/mechanisms/GenericMechanism.js index 74ebdce1..e591606b 100644 --- a/src/components/views/mechanisms/GenericMechanism.js +++ b/src/components/views/mechanisms/GenericMechanism.js @@ -1,10 +1,7 @@ import * as React from "react"; import MechSimple from "./MechSimple"; import MechMetadata from "./MechMetadata"; -import { Rnd } from "react-rnd"; import { withStyles } from "@mui/styles"; -import { Box, Chip } from "@mui/material"; -import MORE_OPTION from "../../../assets/svg/option.svg" import vars from "../../../assets/styles/variables"; const { draggableBg, listItemActiveBg, textWhite, chipTextColor, chipBorderColor } = vars; @@ -97,37 +94,14 @@ class GenericMechanism extends React.Component { render() { const { expanded } = this.state; - const { classes } = this.props; return ( - // <> - // { expanded - // ? ( ) - // : ( ) - // } - // - - { - this.setState({ x: d.x, y: d.y }); - }} - onResizeStop={(e, direction, ref, delta, position) => { - this.setState({ - width: ref.style.width, - height: ref.style.height, - ...position - }); - }} - > - } label="New Comp" color="secondary" /> - { expanded + <> + { expanded ? ( ) : ( ) } - - + ); } } diff --git a/src/model/Interpreter.ts b/src/model/Interpreter.ts index 58d5fcb2..0fe782c8 100644 --- a/src/model/Interpreter.ts +++ b/src/model/Interpreter.ts @@ -1,9 +1,9 @@ import { GVTypes, PNLClasses } from '../constants'; +import { PortTypes } from '@metacell/meta-diagram'; import ProjectionLink from './links/ProjectionLink'; import QueryService from '../services/queryService'; import MechanismNode from './nodes/mechanism/MechanismNode'; import CompositionNode from './nodes/composition/CompositionNode'; -import { PortTypes } from '@metacell/meta-diagram'; const html2json = require('html2json').html2json const typesArray = Object.values(GVTypes); @@ -13,10 +13,15 @@ const parse = require('dotparser'); export default class ModelInterpreter { nativeModel: any; jsonModel: Object; + modelMap: { [key: string]: Map }; constructor(model: any) { this.nativeModel = model; this.jsonModel = this._convertModel(model); + this.modelMap = { + 'nodes': new Map(), + 'links': new Map() + }; } _convertModel(model: any) : Object { @@ -26,13 +31,13 @@ export default class ModelInterpreter { }; parsedModel[PNLClasses.COMPOSITION] = model[PNLClasses.COMPOSITION].map((singleModel: any) => { - const newModel = parse(singleModel).map((elem: any) => ModelInterpreter.castObject(elem)); + const newModel = parse(singleModel).map((elem: any) => ModelInterpreter.castObject(elem, undefined, this.modelMap)); return newModel; }); parsedModel[PNLClasses.MECHANISM] = model[PNLClasses.MECHANISM].map((singleNode: any) => { let tempNode = parse(singleNode)[0].children.filter((elem: { node_id: { id: string; }; }) => elem.node_id.id !== 'graph'); - let newNode = tempNode.map((elem: any) => ModelInterpreter.castObject(elem)); + let newNode = tempNode.map((elem: any) => ModelInterpreter.castObject(elem, undefined, this.modelMap)); return newNode; }); @@ -51,6 +56,10 @@ export default class ModelInterpreter { return this.nativeModel; } + getModelElementsMap() { + return this.modelMap; + } + static parseNodePorts(name: string, type: string): { [key: string]: any } { let ports: { [key: string]: any[] } = { [PortTypes.INPUT_PORT]: [], @@ -80,7 +89,11 @@ export default class ModelInterpreter { return ports; } - static castObject(item: MechanismNode|CompositionNode|ProjectionLink|any) : MechanismNode|CompositionNode|ProjectionLink { + static castObject( + item: MechanismNode|CompositionNode|ProjectionLink|any, + parent: any|undefined, + modelMap: { [key: string]: Map }) + : MechanismNode|CompositionNode|ProjectionLink { let newNode = item; if (item?.type === undefined) { throw new TypeError('type is missing, object cannot be casted to the right class type.'); @@ -94,6 +107,8 @@ export default class ModelInterpreter { [PNLClasses.PROJECTION]: [], [PNLClasses.COMPOSITION]: [], } + newNode = new CompositionNode(item.id, parent, '', false, ports, extra, children); + modelMap['nodes'].set(newNode.getName(), newNode); item.children.forEach((element: any) => { if (element.type === 'attr_stmt') { extra[element.target] = {} @@ -107,15 +122,15 @@ export default class ModelInterpreter { if (typesArray.includes(element.type)) { switch (element.type) { case GVTypes.COMPOSITION: { - children[PNLClasses.COMPOSITION].push(ModelInterpreter.castObject(element)); + children[PNLClasses.COMPOSITION].push(ModelInterpreter.castObject(element, newNode, modelMap)); break; } case GVTypes.MECHANISM: { - children[PNLClasses.MECHANISM].push(ModelInterpreter.castObject(element)); + children[PNLClasses.MECHANISM].push(ModelInterpreter.castObject(element, newNode, modelMap)); break; } case GVTypes.PROJECTION: { - children[PNLClasses.PROJECTION].push(ModelInterpreter.castObject(element)); + children[PNLClasses.PROJECTION].push(ModelInterpreter.castObject(element, newNode, modelMap)); break; } default: @@ -125,7 +140,6 @@ export default class ModelInterpreter { } } }); - newNode = new CompositionNode(item.id, '', false, ports, extra, children); break; } case GVTypes.MECHANISM: { @@ -142,7 +156,8 @@ export default class ModelInterpreter { extra[singleAttr?.id] = singleAttr?.eq; } }); - newNode = new MechanismNode(item?.node_id?.id, '', false, ports, extra); + newNode = new MechanismNode(item?.node_id?.id, parent, '', false, ports, extra); + modelMap['nodes'].set(newNode.getName(), newNode); break; } case GVTypes.PROJECTION: { @@ -165,6 +180,7 @@ export default class ModelInterpreter { receiverPort = item.edge_list[1]['port']['id']; } newNode = new ProjectionLink(name, sender, senderPort, receiver, receiverPort, false, extra); + modelMap['links'].set(newNode.getName(), newNode); break; } default: diff --git a/src/model/nodes/composition/CompositionNode.ts b/src/model/nodes/composition/CompositionNode.ts index 204ff8a1..9a7fb71f 100644 --- a/src/model/nodes/composition/CompositionNode.ts +++ b/src/model/nodes/composition/CompositionNode.ts @@ -1,20 +1,25 @@ -// import MetaDiagram from 'meta-diagram'; -import Port from '../PortNode'; -import PortNode from '../PortNode'; import { PNLClasses } from '../../../constants'; import ModelInterpreter from '../../Interpreter'; import MechanismNode from '../mechanism/MechanismNode'; import ProjectionLink from '../../links/ProjectionLink'; -// import { castObject } from '../../utils'; - +import { MetaNode, MetaPort, Position, PortTypes } from '@metacell/meta-diagram'; export default class CompositionNode extends MechanismNode { children: {[key: string]: any}; childrenMap: Map; innerClass: String; + parent: MechanismNode|CompositionNode|undefined; - constructor(name: string, icon?: string, isExpaded?: boolean, ports?: { [key: string]: Array }, extra?: Object, children?: {[key: string]: any}) { - super(name, icon, isExpaded, ports, extra); + constructor( + name: string, + parent: MechanismNode|CompositionNode|undefined, + icon?: string, + isExpaded?: boolean, + ports?: { [key: string]: Array }, + extra?: Object, + children?: {[key: string]: any}) + { + super(name, parent, icon, isExpaded, ports, extra); this.childrenMap = new Map(); this.children = children !== undefined ? children : { @@ -34,20 +39,20 @@ export default class CompositionNode extends MechanismNode { return; } - const castChild = ModelInterpreter.castObject(child); - this.childrenMap.set(child.id, castChild); + // const castChild = ModelInterpreter.castObject(child, this.parent); + // this.childrenMap.set(child.id, castChild); - switch(castChild.getType()) { - case PNLClasses.COMPOSITION: - this.children[PNLClasses.COMPOSITION].push(castChild); - break; - case PNLClasses.MECHANISM: - this.children[PNLClasses.MECHANISM].push(castChild); - break; - case PNLClasses.PROJECTION: - this.children[PNLClasses.PROJECTION].push(castChild); - break; - } + // switch(castChild.getType()) { + // case PNLClasses.COMPOSITION: + // this.children[PNLClasses.COMPOSITION].push(castChild); + // break; + // case PNLClasses.MECHANISM: + // this.children[PNLClasses.MECHANISM].push(castChild); + // break; + // case PNLClasses.PROJECTION: + // this.children[PNLClasses.PROJECTION].push(castChild); + // break; + // } } getChildren() : {[key: string]: any} { @@ -58,7 +63,33 @@ export default class CompositionNode extends MechanismNode { return this.innerClass; } - getMetaDiagram() : any { - console.log("Composition Node implementation of getMetaDiagram."); + getMetaNode() : any { + // TODO: get position from the graphviz data + let x = 200 + Math.random() * 600; + let y = 200 + Math.random() * 600; + let parent = this.parent ? this.parent.getMetaNode() : undefined; + let ports: Array = [] + // TODO: the MetaPort has the enum prefix cause the projections are created with that prefix + // from the graphviz data we get. + // this.ports[PortTypes.INPUT_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.INPUT_PORT + '-' + port, PortTypes.INPUT_PORT + '-' + port, PortTypes.INPUT_PORT, new Position(0, 0), new Map()))); + // this.ports[PortTypes.OUTPUT_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.OUTPUT_PORT + '-' + port, PortTypes.OUTPUT_PORT + '-' + port, PortTypes.OUTPUT_PORT, new Position(0, 0), new Map()))); + // this.ports[PortTypes.PARAMETER_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT, new Position(0, 0), new Map()))); + return new MetaNode( + this.name, + this.name, + PNLClasses.COMPOSITION, + new Position(x, y), + 'node-gray', + parent, + ports, + new Map(Object.entries({ + name: 'Mechanism Name', + variant: 'node-gray', + pnlClass: 'ProcessingMechanism', + shape: 'circle', + selected: false + }) + ) + ); } } \ No newline at end of file diff --git a/src/model/nodes/mechanism/MechanismNode.ts b/src/model/nodes/mechanism/MechanismNode.ts index 73a75dca..121159ea 100644 --- a/src/model/nodes/mechanism/MechanismNode.ts +++ b/src/model/nodes/mechanism/MechanismNode.ts @@ -1,5 +1,6 @@ import { PNLClasses } from '../../../constants'; import IMetaDiagramConverter from '../IMetaDiagramConverter'; +import CompositionNode from '../composition/CompositionNode'; import { MetaNode, MetaPort, Position, PortTypes } from '@metacell/meta-diagram'; export default class MechanismNode implements IMetaDiagramConverter { @@ -9,14 +10,23 @@ export default class MechanismNode implements IMetaDiagramConverter { ports: { [key: string]: Array } ; extra: Object; innerClass: String; + parent: MechanismNode|CompositionNode|undefined; - constructor(name: string, icon?: string, isExpaded?: boolean, ports?: { [key: string]: Array }, extra?: Object) { + constructor( + name: string, + parent: MechanismNode|CompositionNode|undefined, + icon?: string, + isExpaded?: boolean, + ports?: { [key: string]: Array }, + extra?: Object) + { this.name = name; this.icon = icon !== undefined ? icon : ""; this.ports = ports !== undefined ? ports : {}; this.extra = extra !== undefined ? extra : []; this.isExpanded = isExpaded !== undefined ? isExpaded : false; this.innerClass = PNLClasses.MECHANISM; + this.parent = parent; } getName() : string { @@ -56,18 +66,20 @@ export default class MechanismNode implements IMetaDiagramConverter { // TODO: get position from the graphviz data let x = 200 + Math.random() * 600; let y = 200 + Math.random() * 600; + let parent = this.parent ? this.parent.getMetaNode() : undefined; let ports: Array = [] // TODO: the MetaPort has the enum prefix cause the projections are created with that prefix // from the graphviz data we get. this.ports[PortTypes.INPUT_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.INPUT_PORT + '-' + port, PortTypes.INPUT_PORT + '-' + port, PortTypes.INPUT_PORT, new Position(0, 0), new Map()))); this.ports[PortTypes.OUTPUT_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.OUTPUT_PORT + '-' + port, PortTypes.OUTPUT_PORT + '-' + port, PortTypes.OUTPUT_PORT, new Position(0, 0), new Map()))); - // this.ports[PortTypes.PARAMETER_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT, new Position(0, 0), new Map()))); + this.ports[PortTypes.PARAMETER_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT, new Position(0, 0), new Map()))); return new MetaNode( this.name, this.name, - 'mechanism', + PNLClasses.MECHANISM, new Position(x, y), 'node-gray', + parent, ports, new Map(Object.entries({ name: 'Mechanism Name', diff --git a/src/model/utils.js b/src/model/utils.js index 3a6d2d12..26b25e07 100644 --- a/src/model/utils.js +++ b/src/model/utils.js @@ -48,13 +48,11 @@ export function buildModel(frontendModel, coord, prevModel) { frontendModel[PNLClasses.COMPOSITION]?.forEach( node => { if (Array.isArray(node)) { node.forEach( comp => { - // TODO: create the composition and add it to the model - coordinates.y += 250; + finalModel[PNLClasses.COMPOSITION]?.push(comp.getMetaNode()); buildModel(comp.children, coordinates, finalModel); }); } else { - // TODO: create the composition and add it to the model - coordinates.y += 250; + finalModel[PNLClasses.COMPOSITION]?.push(node.getMetaNode()); buildModel(node.children, coordinates, finalModel); } }); From cc20d77778d3fa56132f49a110d7b77e82f07d71 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Tue, 6 Sep 2022 14:05:18 +0100 Subject: [PATCH 02/14] #36 removed modelMap --- src/model/Interpreter.ts | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/model/Interpreter.ts b/src/model/Interpreter.ts index 0fe782c8..157f9889 100644 --- a/src/model/Interpreter.ts +++ b/src/model/Interpreter.ts @@ -13,15 +13,10 @@ const parse = require('dotparser'); export default class ModelInterpreter { nativeModel: any; jsonModel: Object; - modelMap: { [key: string]: Map }; constructor(model: any) { this.nativeModel = model; this.jsonModel = this._convertModel(model); - this.modelMap = { - 'nodes': new Map(), - 'links': new Map() - }; } _convertModel(model: any) : Object { @@ -31,13 +26,13 @@ export default class ModelInterpreter { }; parsedModel[PNLClasses.COMPOSITION] = model[PNLClasses.COMPOSITION].map((singleModel: any) => { - const newModel = parse(singleModel).map((elem: any) => ModelInterpreter.castObject(elem, undefined, this.modelMap)); + const newModel = parse(singleModel).map((elem: any) => ModelInterpreter.castObject(elem, undefined)); return newModel; }); parsedModel[PNLClasses.MECHANISM] = model[PNLClasses.MECHANISM].map((singleNode: any) => { let tempNode = parse(singleNode)[0].children.filter((elem: { node_id: { id: string; }; }) => elem.node_id.id !== 'graph'); - let newNode = tempNode.map((elem: any) => ModelInterpreter.castObject(elem, undefined, this.modelMap)); + let newNode = tempNode.map((elem: any) => ModelInterpreter.castObject(elem, undefined)); return newNode; }); @@ -56,10 +51,6 @@ export default class ModelInterpreter { return this.nativeModel; } - getModelElementsMap() { - return this.modelMap; - } - static parseNodePorts(name: string, type: string): { [key: string]: any } { let ports: { [key: string]: any[] } = { [PortTypes.INPUT_PORT]: [], @@ -89,11 +80,7 @@ export default class ModelInterpreter { return ports; } - static castObject( - item: MechanismNode|CompositionNode|ProjectionLink|any, - parent: any|undefined, - modelMap: { [key: string]: Map }) - : MechanismNode|CompositionNode|ProjectionLink { + static castObject(item: MechanismNode|CompositionNode|ProjectionLink|any, parent: any|undefined) : MechanismNode|CompositionNode|ProjectionLink { let newNode = item; if (item?.type === undefined) { throw new TypeError('type is missing, object cannot be casted to the right class type.'); @@ -108,7 +95,6 @@ export default class ModelInterpreter { [PNLClasses.COMPOSITION]: [], } newNode = new CompositionNode(item.id, parent, '', false, ports, extra, children); - modelMap['nodes'].set(newNode.getName(), newNode); item.children.forEach((element: any) => { if (element.type === 'attr_stmt') { extra[element.target] = {} @@ -122,15 +108,15 @@ export default class ModelInterpreter { if (typesArray.includes(element.type)) { switch (element.type) { case GVTypes.COMPOSITION: { - children[PNLClasses.COMPOSITION].push(ModelInterpreter.castObject(element, newNode, modelMap)); + children[PNLClasses.COMPOSITION].push(ModelInterpreter.castObject(element, newNode)); break; } case GVTypes.MECHANISM: { - children[PNLClasses.MECHANISM].push(ModelInterpreter.castObject(element, newNode, modelMap)); + children[PNLClasses.MECHANISM].push(ModelInterpreter.castObject(element, newNode)); break; } case GVTypes.PROJECTION: { - children[PNLClasses.PROJECTION].push(ModelInterpreter.castObject(element, newNode, modelMap)); + children[PNLClasses.PROJECTION].push(ModelInterpreter.castObject(element, newNode)); break; } default: @@ -157,7 +143,6 @@ export default class ModelInterpreter { } }); newNode = new MechanismNode(item?.node_id?.id, parent, '', false, ports, extra); - modelMap['nodes'].set(newNode.getName(), newNode); break; } case GVTypes.PROJECTION: { @@ -180,7 +165,6 @@ export default class ModelInterpreter { receiverPort = item.edge_list[1]['port']['id']; } newNode = new ProjectionLink(name, sender, senderPort, receiver, receiverPort, false, extra); - modelMap['links'].set(newNode.getName(), newNode); break; } default: From 80f89118a1bcfc6404ef43af8ce312281dfcd9c7 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Wed, 7 Sep 2022 22:47:20 +0100 Subject: [PATCH 03/14] #36 more on the composition implementation --- src/components/Main.js | 1 + src/model/Interpreter.ts | 37 ++++++++++++------- src/model/links/ProjectionLink.ts | 7 ++-- .../nodes/composition/CompositionNode.ts | 5 --- src/model/nodes/mechanism/MechanismNode.ts | 2 +- 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/components/Main.js b/src/components/Main.js index d079ab17..b2d1a99a 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -33,6 +33,7 @@ class Main extends React.Component { const interpreter = new ModelInterpreter(mockModel); const model = interpreter.getModel(); const metaModel = buildModel(model); + console.log(interpreter.getModelElementsMap()) const componentsMap = new ComponentsMap(new Map(), new Map()); diff --git a/src/model/Interpreter.ts b/src/model/Interpreter.ts index 157f9889..81e46b8e 100644 --- a/src/model/Interpreter.ts +++ b/src/model/Interpreter.ts @@ -5,7 +5,6 @@ import QueryService from '../services/queryService'; import MechanismNode from './nodes/mechanism/MechanismNode'; import CompositionNode from './nodes/composition/CompositionNode'; -const html2json = require('html2json').html2json const typesArray = Object.values(GVTypes); const parse = require('dotparser'); @@ -13,8 +12,13 @@ const parse = require('dotparser'); export default class ModelInterpreter { nativeModel: any; jsonModel: Object; + modelMap: { [key: string]: Map }; constructor(model: any) { + this.modelMap = { + 'nodes': new Map(), + 'links': new Map() + }; this.nativeModel = model; this.jsonModel = this._convertModel(model); } @@ -26,13 +30,13 @@ export default class ModelInterpreter { }; parsedModel[PNLClasses.COMPOSITION] = model[PNLClasses.COMPOSITION].map((singleModel: any) => { - const newModel = parse(singleModel).map((elem: any) => ModelInterpreter.castObject(elem, undefined)); + const newModel = parse(singleModel).map((elem: any) => this.castObject(elem, undefined, this.modelMap)); return newModel; }); parsedModel[PNLClasses.MECHANISM] = model[PNLClasses.MECHANISM].map((singleNode: any) => { let tempNode = parse(singleNode)[0].children.filter((elem: { node_id: { id: string; }; }) => elem.node_id.id !== 'graph'); - let newNode = tempNode.map((elem: any) => ModelInterpreter.castObject(elem, undefined)); + let newNode = tempNode.map((elem: any) => this.castObject(elem, undefined, this.modelMap)); return newNode; }); @@ -51,7 +55,11 @@ export default class ModelInterpreter { return this.nativeModel; } - static parseNodePorts(name: string, type: string): { [key: string]: any } { + getModelElementsMap() { + return this.modelMap; + } + + parseNodePorts(name: string, type: string): { [key: string]: any } { let ports: { [key: string]: any[] } = { [PortTypes.INPUT_PORT]: [], [PortTypes.OUTPUT_PORT]: [], @@ -80,7 +88,11 @@ export default class ModelInterpreter { return ports; } - static castObject(item: MechanismNode|CompositionNode|ProjectionLink|any, parent: any|undefined) : MechanismNode|CompositionNode|ProjectionLink { + castObject( + item: MechanismNode|CompositionNode|ProjectionLink|any, + parent: any|undefined, + modelMap: { [key: string]: Map }) + : MechanismNode|CompositionNode|ProjectionLink { let newNode = item; if (item?.type === undefined) { throw new TypeError('type is missing, object cannot be casted to the right class type.'); @@ -95,6 +107,7 @@ export default class ModelInterpreter { [PNLClasses.COMPOSITION]: [], } newNode = new CompositionNode(item.id, parent, '', false, ports, extra, children); + modelMap['nodes'].set(newNode.getName(), newNode); item.children.forEach((element: any) => { if (element.type === 'attr_stmt') { extra[element.target] = {} @@ -108,15 +121,15 @@ export default class ModelInterpreter { if (typesArray.includes(element.type)) { switch (element.type) { case GVTypes.COMPOSITION: { - children[PNLClasses.COMPOSITION].push(ModelInterpreter.castObject(element, newNode)); + children[PNLClasses.COMPOSITION].push(this.castObject(element, newNode, modelMap)); break; } case GVTypes.MECHANISM: { - children[PNLClasses.MECHANISM].push(ModelInterpreter.castObject(element, newNode)); + children[PNLClasses.MECHANISM].push(this.castObject(element, newNode, modelMap)); break; } case GVTypes.PROJECTION: { - children[PNLClasses.PROJECTION].push(ModelInterpreter.castObject(element, newNode)); + children[PNLClasses.PROJECTION].push(this.castObject(element, newNode, modelMap)); break; } default: @@ -132,17 +145,12 @@ export default class ModelInterpreter { let ports: { [key: string]: any } = this.parseNodePorts(item?.node_id?.id, PNLClasses.MECHANISM); let extra: { [key: string]: any } = {}; item.attr_list.forEach((singleAttr: any) => { - if (singleAttr.id === 'label') { - // TODO: implement the parsing of the json structure generated below - // in order to detect ports and other elements of the node. - let parsedHtml = html2json(singleAttr.eq.value); - // console.log(parsedHtml) - } if (singleAttr.type === 'attr') { extra[singleAttr?.id] = singleAttr?.eq; } }); newNode = new MechanismNode(item?.node_id?.id, parent, '', false, ports, extra); + modelMap['nodes'].set(newNode.getName(), newNode); break; } case GVTypes.PROJECTION: { @@ -165,6 +173,7 @@ export default class ModelInterpreter { receiverPort = item.edge_list[1]['port']['id']; } newNode = new ProjectionLink(name, sender, senderPort, receiver, receiverPort, false, extra); + modelMap['links'].set(newNode.getName(), newNode); break; } default: diff --git a/src/model/links/ProjectionLink.ts b/src/model/links/ProjectionLink.ts index 0ff61f58..8330f718 100644 --- a/src/model/links/ProjectionLink.ts +++ b/src/model/links/ProjectionLink.ts @@ -22,6 +22,10 @@ export default class ProjectionLink implements IMetaLinkConverter { this.extra = extra !== undefined ? extra : {}; this.isExpanded = isExpanded !== undefined ? isExpanded : false; this.innerClass = PNLClasses.PROJECTION; + + if (this.name === '') { + this.name = 'link_' + this.sender + '-to-' + this.receiver; + } } getName() : string { @@ -57,9 +61,6 @@ export default class ProjectionLink implements IMetaLinkConverter { } getMetaLink() : MetaLink { - if (this.name === '') { - this.name = 'link_' + this.sender + '-to-' + this.receiver; - } return new MetaLink( this.name, this.name, diff --git a/src/model/nodes/composition/CompositionNode.ts b/src/model/nodes/composition/CompositionNode.ts index 9a7fb71f..94bd3123 100644 --- a/src/model/nodes/composition/CompositionNode.ts +++ b/src/model/nodes/composition/CompositionNode.ts @@ -69,11 +69,6 @@ export default class CompositionNode extends MechanismNode { let y = 200 + Math.random() * 600; let parent = this.parent ? this.parent.getMetaNode() : undefined; let ports: Array = [] - // TODO: the MetaPort has the enum prefix cause the projections are created with that prefix - // from the graphviz data we get. - // this.ports[PortTypes.INPUT_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.INPUT_PORT + '-' + port, PortTypes.INPUT_PORT + '-' + port, PortTypes.INPUT_PORT, new Position(0, 0), new Map()))); - // this.ports[PortTypes.OUTPUT_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.OUTPUT_PORT + '-' + port, PortTypes.OUTPUT_PORT + '-' + port, PortTypes.OUTPUT_PORT, new Position(0, 0), new Map()))); - // this.ports[PortTypes.PARAMETER_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT, new Position(0, 0), new Map()))); return new MetaNode( this.name, this.name, diff --git a/src/model/nodes/mechanism/MechanismNode.ts b/src/model/nodes/mechanism/MechanismNode.ts index 121159ea..f786e6f7 100644 --- a/src/model/nodes/mechanism/MechanismNode.ts +++ b/src/model/nodes/mechanism/MechanismNode.ts @@ -7,7 +7,7 @@ export default class MechanismNode implements IMetaDiagramConverter { name: string; icon: string; isExpanded: Boolean; - ports: { [key: string]: Array } ; + ports: { [key: string]: Array }; extra: Object; innerClass: String; parent: MechanismNode|CompositionNode|undefined; From feb3dd05ab22e64264a02aea472ed278c1d94dc9 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Thu, 6 Oct 2022 10:12:16 +0100 Subject: [PATCH 04/14] #36 frontend model interpreter refactoring --- src/components/Main.js | 23 +- src/components/views/mechanisms/MechSimple.js | 3 +- src/model/Interpreter.ts | 300 ++- src/model/links/ProjectionLink.ts | 6 +- .../nodes/composition/CompositionNode.ts | 70 +- src/model/nodes/mechanism/MechanismNode.ts | 38 +- src/resources/model.js | 25 +- src/resources/model.json | 2212 +++++++++++++++++ src/resources/single_nodes.json | 592 +++++ src/services/queryService.ts | 3 +- 10 files changed, 3087 insertions(+), 185 deletions(-) create mode 100644 src/resources/model.json create mode 100644 src/resources/single_nodes.json diff --git a/src/components/Main.js b/src/components/Main.js index b2d1a99a..ed489bf5 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -9,7 +9,6 @@ import Composition from './views/compositions/Composition'; import GenericMechanism from './views/mechanisms/GenericMechanism'; import MetaDiagram, { ComponentsMap } from "@metacell/meta-diagram"; import CustomLinkWidget from './views/projections/CustomLinkWidget'; - const mockModel = require('../resources/model').mockModel; const styles = () => ({ @@ -26,24 +25,22 @@ class Main extends React.Component { constructor (props) { super(props); this.state = {}; + this.interpreter = new ModelInterpreter(mockModel); + this.model = this.interpreter.getModel(); + this.metaModel = this.interpreter.getMetaModel(); + this.componentsMap = new ComponentsMap(new Map(), new Map()); + + this.componentsMap.nodes.set(PNLClasses.COMPOSITION, Composition); + this.componentsMap.nodes.set(PNLClasses.MECHANISM, GenericMechanism); + this.componentsMap.links.set(PNLClasses.PROJECTION, CustomLinkWidget); } render() { const { classes } = this.props; - const interpreter = new ModelInterpreter(mockModel); - const model = interpreter.getModel(); - const metaModel = buildModel(model); - console.log(interpreter.getModelElementsMap()) - - const componentsMap = new ComponentsMap(new Map(), new Map()); - - componentsMap.nodes.set(PNLClasses.COMPOSITION, Composition); - componentsMap.nodes.set(PNLClasses.MECHANISM, GenericMechanism); - componentsMap.links.set(PNLClasses.PROJECTION, CustomLinkWidget); return (
- ); default: - console.log('different port found' + port.getName() + ' ' + port.getType()); + // TODO: what to do with other ports? + // console.log('different port found' + port.getName() + ' ' + port.getType()); } })} diff --git a/src/model/Interpreter.ts b/src/model/Interpreter.ts index 81e46b8e..b2c7857e 100644 --- a/src/model/Interpreter.ts +++ b/src/model/Interpreter.ts @@ -1,46 +1,51 @@ -import { GVTypes, PNLClasses } from '../constants'; -import { PortTypes } from '@metacell/meta-diagram'; +import { PNLClasses } from '../constants'; +import { MetaLink, MetaNode, PortTypes } from '@metacell/meta-diagram'; import ProjectionLink from './links/ProjectionLink'; import QueryService from '../services/queryService'; import MechanismNode from './nodes/mechanism/MechanismNode'; import CompositionNode from './nodes/composition/CompositionNode'; -const typesArray = Object.values(GVTypes); -const parse = require('dotparser'); - - export default class ModelInterpreter { nativeModel: any; jsonModel: Object; modelMap: { [key: string]: Map }; + pnlModel: { [key: string]: Array }; + metaModel: { [key: string]: Array }; + nodeIdsMap: Map; + linkIdsMap: Map; constructor(model: any) { this.modelMap = { 'nodes': new Map(), 'links': new Map() }; + this.pnlModel = { + [PNLClasses.COMPOSITION]: [], + [PNLClasses.MECHANISM]: [], + [PNLClasses.PROJECTION]: [], + }; + this.metaModel = { + [PNLClasses.COMPOSITION]: [], + [PNLClasses.MECHANISM]: [], + [PNLClasses.PROJECTION]: [], + }; + this.nodeIdsMap = new Map(); + this.linkIdsMap = new Map(); this.nativeModel = model; this.jsonModel = this._convertModel(model); } _convertModel(model: any) : Object { - const parsedModel = { - [PNLClasses.COMPOSITION]: [], - [PNLClasses.MECHANISM]: [], - }; - - parsedModel[PNLClasses.COMPOSITION] = model[PNLClasses.COMPOSITION].map((singleModel: any) => { - const newModel = parse(singleModel).map((elem: any) => this.castObject(elem, undefined, this.modelMap)); - return newModel; + model[PNLClasses.COMPOSITION].forEach((singleModel: any) => { + this.nodeIdsMap = new Map(); + this.linkIdsMap = new Map(); + this.castComposition(singleModel, undefined, this.modelMap); }); - - parsedModel[PNLClasses.MECHANISM] = model[PNLClasses.MECHANISM].map((singleNode: any) => { - let tempNode = parse(singleNode)[0].children.filter((elem: { node_id: { id: string; }; }) => elem.node_id.id !== 'graph'); - let newNode = tempNode.map((elem: any) => this.castObject(elem, undefined, this.modelMap)); - return newNode; + model[PNLClasses.MECHANISM].forEach((singleNode: any) => { + this.castMechanism(singleNode, undefined, this.modelMap); }); - - return parsedModel; + this.setMetaModel() + return this.pnlModel; } updateModel(newModel: any) { @@ -51,6 +56,22 @@ export default class ModelInterpreter { return this.jsonModel; } + setMetaModel() { + this.metaModel[PNLClasses.COMPOSITION] = this.pnlModel[PNLClasses.COMPOSITION].map( + (item:CompositionNode) => item.getMetaNode() + ); + this.metaModel[PNLClasses.MECHANISM] = this.pnlModel[PNLClasses.MECHANISM].map( + (item:MechanismNode) => item.getMetaNode() + ); + this.metaModel[PNLClasses.PROJECTION] = this.pnlModel[PNLClasses.PROJECTION].map( + (item:ProjectionLink) => item.getMetaLink() + ); + } + + getMetaModel() { + return this.metaModel; + } + getNativeModel() { return this.nativeModel; } @@ -88,99 +109,166 @@ export default class ModelInterpreter { return ports; } - castObject( + castComposition( item: MechanismNode|CompositionNode|ProjectionLink|any, parent: any|undefined, modelMap: { [key: string]: Map }) - : MechanismNode|CompositionNode|ProjectionLink { + : MechanismNode|CompositionNode|ProjectionLink { let newNode = item; - if (item?.type === undefined) { - throw new TypeError('type is missing, object cannot be casted to the right class type.'); + let extra: { [key: string]: any } = {}; + let ports : any = []; + let boundingBox = { + llx: 0, + lly: 0, + urx: 0, + ury: 0, + }; + if (item?.bb) { + let _vertices = item.bb.split(','); + boundingBox.llx = _vertices[0]; + boundingBox.lly = _vertices[1]; + boundingBox.urx = _vertices[2]; + boundingBox.ury = _vertices[3]; } - switch (item.type) { - case GVTypes.COMPOSITION: { - let extra: { [key: string]: any } = {}; - let ports : any = []; - let children: { [key: string]: any } = { - [PNLClasses.MECHANISM]: [], - [PNLClasses.PROJECTION]: [], - [PNLClasses.COMPOSITION]: [], - } - newNode = new CompositionNode(item.id, parent, '', false, ports, extra, children); - modelMap['nodes'].set(newNode.getName(), newNode); - item.children.forEach((element: any) => { - if (element.type === 'attr_stmt') { - extra[element.target] = {} - element.attr_list.forEach( (innerElement: any) => { - if (innerElement.type === 'attr') { - extra[element.target][innerElement?.id] = innerElement?.eq; - } - }); - return; - } - if (typesArray.includes(element.type)) { - switch (element.type) { - case GVTypes.COMPOSITION: { - children[PNLClasses.COMPOSITION].push(this.castObject(element, newNode, modelMap)); - break; - } - case GVTypes.MECHANISM: { - children[PNLClasses.MECHANISM].push(this.castObject(element, newNode, modelMap)); - break; - } - case GVTypes.PROJECTION: { - children[PNLClasses.PROJECTION].push(this.castObject(element, newNode, modelMap)); - break; - } - default: - // TODO: enable this in the future - // throw new Error(`Casting error, "${item.type}" type not known.`); - console.log(`Casting error, "${item.type}" type not known.`); - } - } - }); - break; + newNode = new CompositionNode(item?.name, parent, '', false, ports, extra, undefined, boundingBox); + modelMap['nodes'].set(newNode.getName(), newNode); + // temp array to host all the nested compositions + let childrenCompositions: Array = []; + + // we iterate the objects and first identify all the mechanisms + item.objects.forEach((child: any) => { + let newChild = undefined; + if (child.rankdir) { + // we park for now nested compositions + childrenCompositions.push(child) + } else { + newChild = this.castMechanism(child, newNode, this.modelMap); + newNode.addChild(newChild); } - case GVTypes.MECHANISM: { - let ports: { [key: string]: any } = this.parseNodePorts(item?.node_id?.id, PNLClasses.MECHANISM); - let extra: { [key: string]: any } = {}; - item.attr_list.forEach((singleAttr: any) => { - if (singleAttr.type === 'attr') { - extra[singleAttr?.id] = singleAttr?.eq; - } - }); - newNode = new MechanismNode(item?.node_id?.id, parent, '', false, ports, extra); - modelMap['nodes'].set(newNode.getName(), newNode); - break; + if (newChild && !this.nodeIdsMap.has(child?._gvid)) { + this.nodeIdsMap.set(child?._gvid, newChild); } - case GVTypes.PROJECTION: { - let name = ''; - let extra: { [key: string]: any } = {}; - let sender, senderPort, receiver, receiverPort; - item.attr_list.forEach((singleAttr: any) => { - if (singleAttr.id === 'label') { - name = singleAttr.eq; - return; - } - if (singleAttr.type === 'attr') { - extra[singleAttr?.id] = singleAttr?.eq; - } - }); - if (item.edge_list.length === 2) { - sender = item.edge_list[0].id; - senderPort = item.edge_list[0]['port']['id']; - receiver = item.edge_list[1].id; - receiverPort = item.edge_list[1]['port']['id']; - } - newNode = new ProjectionLink(name, sender, senderPort, receiver, receiverPort, false, extra); - modelMap['links'].set(newNode.getName(), newNode); - break; + }); + + // Now da we have all the mechanisms in the idsMap we continue with the compositions + childrenCompositions.forEach((child: any) => { + let newChild = undefined; + newChild = this.nestedComposition(child, newNode, this.modelMap); + newNode.addChild(newChild); + + if (newChild && !this.nodeIdsMap.has(child?._gvid)) { + this.nodeIdsMap.set(child?._gvid, newChild); } - default: - // TODO: enable this in the future - // throw new Error(`Casting error, "${item.type}" type not known.`); - console.log(`Casting error, "${item.type}" type not known.`); + }); + + item.edges.forEach((edge: any) => { + let tail = this.nodeIdsMap.get(edge.tail); + let head = this.nodeIdsMap.get(edge.head); + // TODO: adjust the cast method below with tail and head + let newChild = this.castEdge(edge, tail, head, newNode, this.modelMap); + if (newChild && !this.linkIdsMap.has(edge?._gvid)) { + this.linkIdsMap.set(edge?._gvid, newChild); + } + newNode.addChild(newChild); + }); + + this.pnlModel[PNLClasses.COMPOSITION].push(newNode); + return newNode; + } + + nestedComposition( + item: MechanismNode|CompositionNode|ProjectionLink|any, + parent: CompositionNode, + modelMap: { [key: string]: Map }) + : MechanismNode|CompositionNode|ProjectionLink { + let newNode = item; + let extra: { [key: string]: any } = {}; + let ports : any = []; + let boundingBox = { + llx: 0, + lly: 0, + urx: 0, + ury: 0, + }; + if (item?.bb) { + let _vertices = item.bb.split(','); + boundingBox.llx = _vertices[0]; + boundingBox.lly = _vertices[1]; + boundingBox.urx = _vertices[2]; + boundingBox.ury = _vertices[3]; } + newNode = new CompositionNode(item?.name, parent, '', false, ports, extra, undefined, boundingBox); + modelMap['nodes'].set(newNode.getName(), newNode); + + // Iterates nodes of the nested composition to fill the children map/array + item.nodes.forEach((id: any) => { + let child = this.nodeIdsMap.get(id); + child.setParent(newNode); + }); + + item.edges.forEach((id: any) => { + // TODO: we should change the paternity of the link but not really needed now. + }); + + this.pnlModel[PNLClasses.COMPOSITION].push(newNode); return newNode; } -} \ No newline at end of file + + castMechanism( + item: MechanismNode|CompositionNode|ProjectionLink|any, + parent: CompositionNode|undefined, + modelMap: { [key: string]: Map }) + : MechanismNode|CompositionNode|ProjectionLink { + let newNode = item; + let ports: { [key: string]: any } = this.parseNodePorts(item?.name, PNLClasses.MECHANISM); + let extra: { [key: string]: any } = {}; + let coordinates = item.pos.split(','); + newNode = new MechanismNode( + item?.name, + parent, + '', + false, + ports, + extra, + coordinates[0], + coordinates[1], + ); + modelMap['nodes'].set(newNode.getName(), newNode); + this.pnlModel[PNLClasses.MECHANISM].push(newNode); + return newNode; + } + + castEdge( + item: MechanismNode|CompositionNode|ProjectionLink|any, + sender: MechanismNode|CompositionNode, + receiver: MechanismNode|CompositionNode, + parent: CompositionNode|undefined, + modelMap: { [key: string]: Map }) + : MechanismNode|CompositionNode|ProjectionLink { + let newNode = item; + let name = ''; + let extra: { [key: string]: any } = {}; + let senderPortName, receiverPortName; + // senderPortName = this.getPortName(item.tailport); + // receiverPortName = this.getPortName(item.headport); + senderPortName = item.tailport; + receiverPortName = item.headport; + newNode = new ProjectionLink( + name, + sender?.getName(), + senderPortName, + receiver.getName(), + receiverPortName, + false, + extra + ); + modelMap['links'].set(newNode.getName(), newNode); + this.pnlModel[PNLClasses.PROJECTION].push(newNode); + return newNode; + } + + getPortName(portString: string): string { + const [portType, ...portName] = portString.split('-'); + return portName.join('-'); + } +} diff --git a/src/model/links/ProjectionLink.ts b/src/model/links/ProjectionLink.ts index 8330f718..75c517a4 100644 --- a/src/model/links/ProjectionLink.ts +++ b/src/model/links/ProjectionLink.ts @@ -11,7 +11,7 @@ export default class ProjectionLink implements IMetaLinkConverter { receiverPort: string; extra: Object; isExpanded: Boolean; - innerClass: String; + innerClass: string; constructor(name: string, sender: string, senderPort: string, receiver: string, receiverPort: string, isExpanded?: Boolean, extra?: Object) { this.name = name; @@ -78,7 +78,7 @@ export default class ProjectionLink implements IMetaLinkConverter { ); } - getType(): String { + getType(): string { return this.innerClass; } -} \ No newline at end of file +} diff --git a/src/model/nodes/composition/CompositionNode.ts b/src/model/nodes/composition/CompositionNode.ts index 94bd3123..20574da6 100644 --- a/src/model/nodes/composition/CompositionNode.ts +++ b/src/model/nodes/composition/CompositionNode.ts @@ -6,60 +6,70 @@ import { MetaNode, MetaPort, Position, PortTypes } from '@metacell/meta-diagram' export default class CompositionNode extends MechanismNode { children: {[key: string]: any}; - childrenMap: Map; - innerClass: String; - parent: MechanismNode|CompositionNode|undefined; + childrenMap: Map; + innerClass: string; + boundingBox: {[key: string]: Number}|undefined; constructor( name: string, - parent: MechanismNode|CompositionNode|undefined, + parent: CompositionNode|undefined, icon?: string, isExpaded?: boolean, ports?: { [key: string]: Array }, extra?: Object, - children?: {[key: string]: any}) + children?: {[key: string]: any}, + boundingBox?: {[key: string]: Number}|undefined) { super(name, parent, icon, isExpaded, ports, extra); this.childrenMap = new Map(); this.children = children !== undefined ? children : { - 'mechanisms': [], - 'projections': [], - 'compositions': [], + [PNLClasses.MECHANISM]: [], + [PNLClasses.PROJECTION]: [], + [PNLClasses.COMPOSITION]: [], }; this.innerClass = PNLClasses.COMPOSITION; + this.boundingBox = boundingBox; } - addChild(child: any) { - if (child?.id === undefined) { - throw new TypeError('Each child should have a unique id string.'); + addChild(child: MechanismNode|CompositionNode|ProjectionLink) { + if (!this.childrenMap.has(child.getName())) { + this.childrenMap.set(child.getName(), child); } + this.children[child.getType()].push(child); + } - if (this.childrenMap.has(child.id)) { - return; + removeChild(child: MechanismNode|CompositionNode|ProjectionLink) { + if (this.childrenMap.has(child.getName())) { + this.childrenMap.delete(child.getName()); + } + switch (child.getType()) { + case PNLClasses.MECHANISM: { + this.children[PNLClasses.MECHANISM] = this.children[PNLClasses.MECHANISM].filter( (item: MechanismNode) => { + return item.getName() !== child.getName() + }) + break; + } + case PNLClasses.COMPOSITION: { + this.children[PNLClasses.COMPOSITION] = this.children[PNLClasses.COMPOSITION].filter( (item: MechanismNode) => { + return item.getName() !== child.getName() + }) + break; + } + case PNLClasses.PROJECTION: { + this.children[PNLClasses.PROJECTION] = this.children[PNLClasses.PROJECTION].filter( (item: MechanismNode) => { + return item.getName() !== child.getName() + }) + break; + } } - - // const castChild = ModelInterpreter.castObject(child, this.parent); - // this.childrenMap.set(child.id, castChild); - - // switch(castChild.getType()) { - // case PNLClasses.COMPOSITION: - // this.children[PNLClasses.COMPOSITION].push(castChild); - // break; - // case PNLClasses.MECHANISM: - // this.children[PNLClasses.MECHANISM].push(castChild); - // break; - // case PNLClasses.PROJECTION: - // this.children[PNLClasses.PROJECTION].push(castChild); - // break; - // } } getChildren() : {[key: string]: any} { return this.children; } - getType(): String { + getType(): string { return this.innerClass; } @@ -87,4 +97,4 @@ export default class CompositionNode extends MechanismNode { ) ); } -} \ No newline at end of file +} diff --git a/src/model/nodes/mechanism/MechanismNode.ts b/src/model/nodes/mechanism/MechanismNode.ts index f786e6f7..45f29db5 100644 --- a/src/model/nodes/mechanism/MechanismNode.ts +++ b/src/model/nodes/mechanism/MechanismNode.ts @@ -9,16 +9,20 @@ export default class MechanismNode implements IMetaDiagramConverter { isExpanded: Boolean; ports: { [key: string]: Array }; extra: Object; - innerClass: String; - parent: MechanismNode|CompositionNode|undefined; + innerClass: string; + parent: CompositionNode|undefined; + xPosition: number; + yPosition: number; constructor( name: string, - parent: MechanismNode|CompositionNode|undefined, + parent: CompositionNode|undefined, icon?: string, isExpaded?: boolean, ports?: { [key: string]: Array }, - extra?: Object) + extra?: Object, + xPosition?: number, + yPosition?: number) { this.name = name; this.icon = icon !== undefined ? icon : ""; @@ -27,6 +31,8 @@ export default class MechanismNode implements IMetaDiagramConverter { this.isExpanded = isExpaded !== undefined ? isExpaded : false; this.innerClass = PNLClasses.MECHANISM; this.parent = parent; + this.xPosition = xPosition || Math.random() * 900; + this.yPosition = yPosition || Math.random() * 900; } getName() : string { @@ -62,10 +68,19 @@ export default class MechanismNode implements IMetaDiagramConverter { return this.ports; } + setParent(newParent : CompositionNode) { + if (this.parent) { + this.parent.removeChild(this); + } + this.parent = newParent; + this.parent.addChild(this); + } + + getParent() : MechanismNode|CompositionNode|undefined { + return this.parent; + } + getMetaNode() : MetaNode { - // TODO: get position from the graphviz data - let x = 200 + Math.random() * 600; - let y = 200 + Math.random() * 600; let parent = this.parent ? this.parent.getMetaNode() : undefined; let ports: Array = [] // TODO: the MetaPort has the enum prefix cause the projections are created with that prefix @@ -73,11 +88,14 @@ export default class MechanismNode implements IMetaDiagramConverter { this.ports[PortTypes.INPUT_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.INPUT_PORT + '-' + port, PortTypes.INPUT_PORT + '-' + port, PortTypes.INPUT_PORT, new Position(0, 0), new Map()))); this.ports[PortTypes.OUTPUT_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.OUTPUT_PORT + '-' + port, PortTypes.OUTPUT_PORT + '-' + port, PortTypes.OUTPUT_PORT, new Position(0, 0), new Map()))); this.ports[PortTypes.PARAMETER_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT, new Position(0, 0), new Map()))); + // this.ports[PortTypes.INPUT_PORT].forEach((port: any) => ports.push(new MetaPort(port, port, PortTypes.INPUT_PORT, new Position(0, 0), new Map()))); + // this.ports[PortTypes.OUTPUT_PORT].forEach((port: any) => ports.push(new MetaPort(port, port, PortTypes.OUTPUT_PORT, new Position(0, 0), new Map()))); + // this.ports[PortTypes.PARAMETER_PORT].forEach((port: any) => ports.push(new MetaPort(port, port, PortTypes.PARAMETER_PORT, new Position(0, 0), new Map()))); return new MetaNode( this.name, this.name, PNLClasses.MECHANISM, - new Position(x, y), + new Position(this.xPosition, this.yPosition), 'node-gray', parent, ports, @@ -92,7 +110,7 @@ export default class MechanismNode implements IMetaDiagramConverter { ); } - getType() : String { + getType() : string { return this.innerClass; } -} \ No newline at end of file +} diff --git a/src/resources/model.js b/src/resources/model.js index e506dbb3..51243f1a 100644 --- a/src/resources/model.js +++ b/src/resources/model.js @@ -1,27 +1,10 @@ +const modelJson = require('./model.json'); +const singleNodes = require('./single_nodes.json'); const { PNLClasses } = require("../constants"); -const composition = `digraph "Composition-0" { ` + -` graph [label="Composition-0" overlap=False rankdir=BT] ` + -` node [color=black fontname=arial fontsize=12 penwidth=1 shape=record]` + -` edge [fontname=arial fontsize=10]` + -` input [label=<
OutputPort-0
OutputPorts
Mechanism:
input
ParameterPorts
intercept
slope
InputPorts
InputPort-0
> color=green penwidth=3 rank=source shape=plaintext]` + -` mid [label=<
OutputPort-0
OutputPorts
Mechanism:
mid
ParameterPorts
intercept
slope
InputPorts
InputPort-0
> color=black penwidth=1 rank=same shape=plaintext]` + -` input:"OutputPort-OutputPort-0" -> mid:"InputPort-InputPort-0" [label="" arrowhead=normal color=black penwidth=1]` + -` mid:"OutputPort-OutputPort-0" -> output:"InputPort-InputPort-0" [label="" arrowhead=normal color=black penwidth=1]` + -` output [label=<
OutputPort-0
OutputPorts
Mechanism:
output
ParameterPorts
intercept
slope
InputPorts
InputPort-0
> color=red penwidth=3 rank=max shape=plaintext]` + -`}`; - -// TODO: keep in mind that this below has been generated by _show_structure has suggested, but it required -// being incapsulated into the digraph in order to be consumed by the library we are using. -// Also in the interpreter we are purging the artificial graph and the first node for this same reason -// what done manually here will have to be implemented backend wise if there are free mechanisms outside composition. -const singleNode = `digraph "single_node" { graph` + -` single_node [label=<
OutputPort-0
=[0.]
single_node
=[[0.]]
intercept
=[0.]
slope
=[2.]
InputPort-0
=[0.]
>]` + -`}`; - const model = { - [PNLClasses.COMPOSITION]: [composition], - [PNLClasses.MECHANISM]: [singleNode] + [PNLClasses.COMPOSITION]: [modelJson], + [PNLClasses.MECHANISM]: singleNodes.objects } module.exports = { diff --git a/src/resources/model.json b/src/resources/model.json new file mode 100644 index 00000000..0a214842 --- /dev/null +++ b/src/resources/model.json @@ -0,0 +1,2212 @@ +{ + "name": "Composition-0", + "directed": true, + "strict": false, + "_draw_": [ + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#ffffff" + }, + { + "op": "P", + "points": [ + [ + 0.0, + 0.0 + ], + [ + 0.0, + 619.0 + ], + [ + 266.0, + 619.0 + ], + [ + 266.0, + 0.0 + ] + ] + } + ], + "_ldraw_": [ + { + "op": "F", + "size": 14.0, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 133.0, + 7.8 + ], + "align": "c", + "width": 82.0, + "text": "Composition-0" + } + ], + "bb": "0,0,266,619", + "label": "Composition-0", + "lheight": "0.21", + "lp": "133,11.5", + "lwidth": "1.14", + "overlap": "False", + "rankdir": "BT", + "xdotversion": "1.7", + "_subgraph_cnt": 0, + "objects": [ + { + "_gvid": 0, + "name": "input", + "_ldraw_": [ + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fffff0" + }, + { + "op": "P", + "points": [ + [ + 8.0, + 27.0 + ], + [ + 8.0, + 195.0 + ], + [ + 258.0, + 195.0 + ], + [ + 258.0, + 27.0 + ] + ] + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 14.0, + 141.0 + ], + [ + 14.0, + 189.0 + ], + [ + 252.0, + 189.0 + ], + [ + 252.0, + 141.0 + ] + ] + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 18.0, + 164.0 + ], + [ + 18.0, + 185.0 + ], + [ + 248.0, + 185.0 + ], + [ + 248.0, + 164.0 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 19.0, + 165.0 + ], + [ + 19.0, + 184.0 + ], + [ + 247.0, + 184.0 + ], + [ + 247.0, + 165.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 96.0, + 172.4 + ], + "align": "l", + "width": 74.0, + "text": "OutputPort-0" + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 3 + }, + { + "op": "T", + "pt": [ + 98.0, + 149.4 + ], + "align": "l", + "width": 70.0, + "text": "OutputPorts" + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 14.0, + 116.4 + ], + "align": "l", + "width": 66.0, + "text": "Mechanism" + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 80.0, + 116.4 + ], + "align": "l", + "width": 4.0, + "text": ":" + }, + { + "op": "F", + "size": 16.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 29.0, + 101.2 + ], + "align": "l", + "width": 40.0, + "text": "input" + }, + { + "op": "S", + "style": "setlinewidth(1)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 89.0, + 86.0 + ], + [ + 89.0, + 136.0 + ], + [ + 252.0, + 136.0 + ], + [ + 252.0, + 86.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 3 + }, + { + "op": "T", + "pt": [ + 93.0, + 108.9 + ], + "align": "l", + "width": 90.0, + "text": "ParameterPorts" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 189.0, + 90.0 + ], + [ + 189.0, + 132.0 + ], + [ + 248.0, + 132.0 + ], + [ + 248.0, + 90.0 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 190.0, + 112.0 + ], + [ + 190.0, + 131.0 + ], + [ + 247.0, + 131.0 + ], + [ + 247.0, + 112.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 193.0, + 119.4 + ], + "align": "l", + "width": 51.0, + "text": "intercept" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 190.0, + 91.0 + ], + [ + 190.0, + 110.0 + ], + [ + 247.0, + 110.0 + ], + [ + 247.0, + 91.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 202.5, + 98.4 + ], + "align": "l", + "width": 32.0, + "text": "slope" + }, + { + "op": "S", + "style": "setlinewidth(1)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 14.0, + 33.0 + ], + [ + 14.0, + 81.0 + ], + [ + 252.0, + 81.0 + ], + [ + 252.0, + 33.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 3 + }, + { + "op": "T", + "pt": [ + 102.5, + 68.4 + ], + "align": "l", + "width": 61.0, + "text": "InputPorts" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 18.0, + 37.0 + ], + [ + 18.0, + 58.0 + ], + [ + 248.0, + 58.0 + ], + [ + 248.0, + 37.0 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 19.0, + 38.0 + ], + [ + 19.0, + 57.0 + ], + [ + 247.0, + 57.0 + ], + [ + 247.0, + 38.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 101.0, + 45.4 + ], + "align": "l", + "width": 64.0, + "text": "InputPort-0" + }, + { + "op": "S", + "style": "setlinewidth(3)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#00ff00" + }, + { + "op": "p", + "points": [ + [ + 9.5, + 28.5 + ], + [ + 9.5, + 193.5 + ], + [ + 256.5, + 193.5 + ], + [ + 256.5, + 28.5 + ] + ] + } + ], + "color": "green", + "fontname": "arial", + "fontsize": "12", + "height": "2.4444", + "label": "
OutputPort-0
OutputPorts
Mechanism:
input
ParameterPorts
intercept
slope
InputPorts
InputPort-0
", + "penwidth": "3", + "pos": "133,111", + "rank": "source", + "shape": "plaintext", + "width": "3.6944" + }, + { + "_gvid": 1, + "name": "mid", + "_ldraw_": [ + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fffff0" + }, + { + "op": "P", + "points": [ + [ + 10.0, + 239.0 + ], + [ + 10.0, + 403.0 + ], + [ + 256.0, + 403.0 + ], + [ + 256.0, + 239.0 + ] + ] + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 14.0, + 351.0 + ], + [ + 14.0, + 399.0 + ], + [ + 252.0, + 399.0 + ], + [ + 252.0, + 351.0 + ] + ] + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 18.0, + 374.0 + ], + [ + 18.0, + 395.0 + ], + [ + 248.0, + 395.0 + ], + [ + 248.0, + 374.0 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 19.0, + 375.0 + ], + [ + 19.0, + 394.0 + ], + [ + 247.0, + 394.0 + ], + [ + 247.0, + 375.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 96.0, + 382.4 + ], + "align": "l", + "width": 74.0, + "text": "OutputPort-0" + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 3 + }, + { + "op": "T", + "pt": [ + 98.0, + 359.4 + ], + "align": "l", + "width": 70.0, + "text": "OutputPorts" + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 14.0, + 326.4 + ], + "align": "l", + "width": 66.0, + "text": "Mechanism" + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 80.0, + 326.4 + ], + "align": "l", + "width": 4.0, + "text": ":" + }, + { + "op": "F", + "size": 16.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 34.5, + 311.2 + ], + "align": "l", + "width": 29.0, + "text": "mid" + }, + { + "op": "S", + "style": "setlinewidth(1)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 89.0, + 296.0 + ], + [ + 89.0, + 346.0 + ], + [ + 252.0, + 346.0 + ], + [ + 252.0, + 296.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 3 + }, + { + "op": "T", + "pt": [ + 93.0, + 318.9 + ], + "align": "l", + "width": 90.0, + "text": "ParameterPorts" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 189.0, + 300.0 + ], + [ + 189.0, + 342.0 + ], + [ + 248.0, + 342.0 + ], + [ + 248.0, + 300.0 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 190.0, + 322.0 + ], + [ + 190.0, + 341.0 + ], + [ + 247.0, + 341.0 + ], + [ + 247.0, + 322.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 193.0, + 329.4 + ], + "align": "l", + "width": 51.0, + "text": "intercept" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 190.0, + 301.0 + ], + [ + 190.0, + 320.0 + ], + [ + 247.0, + 320.0 + ], + [ + 247.0, + 301.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 202.5, + 308.4 + ], + "align": "l", + "width": 32.0, + "text": "slope" + }, + { + "op": "S", + "style": "setlinewidth(1)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 14.0, + 243.0 + ], + [ + 14.0, + 291.0 + ], + [ + 252.0, + 291.0 + ], + [ + 252.0, + 243.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 3 + }, + { + "op": "T", + "pt": [ + 102.5, + 278.4 + ], + "align": "l", + "width": 61.0, + "text": "InputPorts" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 18.0, + 247.0 + ], + [ + 18.0, + 268.0 + ], + [ + 248.0, + 268.0 + ], + [ + 248.0, + 247.0 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 19.0, + 248.0 + ], + [ + 19.0, + 267.0 + ], + [ + 247.0, + 267.0 + ], + [ + 247.0, + 248.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 101.0, + 255.4 + ], + "align": "l", + "width": 64.0, + "text": "InputPort-0" + }, + { + "op": "S", + "style": "setlinewidth(1)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "p", + "points": [ + [ + 10.0, + 239.0 + ], + [ + 10.0, + 403.0 + ], + [ + 256.0, + 403.0 + ], + [ + 256.0, + 239.0 + ] + ] + } + ], + "color": "black", + "fontname": "arial", + "fontsize": "12", + "height": "2.3889", + "label": "
OutputPort-0
OutputPorts
Mechanism:
mid
ParameterPorts
intercept
slope
InputPorts
InputPort-0
", + "penwidth": "1", + "pos": "133,321", + "rank": "same", + "shape": "plaintext", + "width": "3.6389" + }, + { + "_gvid": 2, + "name": "output", + "_ldraw_": [ + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fffff0" + }, + { + "op": "P", + "points": [ + [ + 8.0, + 447.0 + ], + [ + 8.0, + 615.0 + ], + [ + 258.0, + 615.0 + ], + [ + 258.0, + 447.0 + ] + ] + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 14.0, + 561.0 + ], + [ + 14.0, + 609.0 + ], + [ + 252.0, + 609.0 + ], + [ + 252.0, + 561.0 + ] + ] + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 18.0, + 584.0 + ], + [ + 18.0, + 605.0 + ], + [ + 248.0, + 605.0 + ], + [ + 248.0, + 584.0 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 19.0, + 585.0 + ], + [ + 19.0, + 604.0 + ], + [ + 247.0, + 604.0 + ], + [ + 247.0, + 585.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 96.0, + 592.4 + ], + "align": "l", + "width": 74.0, + "text": "OutputPort-0" + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 3 + }, + { + "op": "T", + "pt": [ + 98.0, + 569.4 + ], + "align": "l", + "width": 70.0, + "text": "OutputPorts" + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 14.0, + 536.4 + ], + "align": "l", + "width": 66.0, + "text": "Mechanism" + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 80.0, + 536.4 + ], + "align": "l", + "width": 4.0, + "text": ":" + }, + { + "op": "F", + "size": 16.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 24.0, + 521.2 + ], + "align": "l", + "width": 50.0, + "text": "output" + }, + { + "op": "S", + "style": "setlinewidth(1)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 89.0, + 506.0 + ], + [ + 89.0, + 556.0 + ], + [ + 252.0, + 556.0 + ], + [ + 252.0, + 506.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 3 + }, + { + "op": "T", + "pt": [ + 93.0, + 528.9 + ], + "align": "l", + "width": 90.0, + "text": "ParameterPorts" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 189.0, + 510.0 + ], + [ + 189.0, + 552.0 + ], + [ + 248.0, + 552.0 + ], + [ + 248.0, + 510.0 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 190.0, + 532.0 + ], + [ + 190.0, + 551.0 + ], + [ + 247.0, + 551.0 + ], + [ + 247.0, + 532.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 193.0, + 539.4 + ], + "align": "l", + "width": 51.0, + "text": "intercept" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 190.0, + 511.0 + ], + [ + 190.0, + 530.0 + ], + [ + 247.0, + 530.0 + ], + [ + 247.0, + 511.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 202.5, + 518.4 + ], + "align": "l", + "width": 32.0, + "text": "slope" + }, + { + "op": "S", + "style": "setlinewidth(1)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 14.0, + 453.0 + ], + [ + 14.0, + 501.0 + ], + [ + 252.0, + 501.0 + ], + [ + 252.0, + 453.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 3 + }, + { + "op": "T", + "pt": [ + 102.5, + 488.4 + ], + "align": "l", + "width": 61.0, + "text": "InputPorts" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 18.0, + 457.0 + ], + [ + 18.0, + 478.0 + ], + [ + 248.0, + 478.0 + ], + [ + 248.0, + 457.0 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 19.0, + 458.0 + ], + [ + 19.0, + 477.0 + ], + [ + 247.0, + 477.0 + ], + [ + 247.0, + 458.0 + ] + ] + }, + { + "op": "F", + "size": 12.0, + "face": "arial" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 101.0, + 465.4 + ], + "align": "l", + "width": 64.0, + "text": "InputPort-0" + }, + { + "op": "S", + "style": "setlinewidth(3)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#ff0000" + }, + { + "op": "p", + "points": [ + [ + 9.5, + 448.5 + ], + [ + 9.5, + 613.5 + ], + [ + 256.5, + 613.5 + ], + [ + 256.5, + 448.5 + ] + ] + } + ], + "color": "red", + "fontname": "arial", + "fontsize": "12", + "height": "2.4444", + "label": "
OutputPort-0
OutputPorts
Mechanism:
output
ParameterPorts
intercept
slope
InputPorts
InputPort-0
", + "penwidth": "3", + "pos": "133,531", + "rank": "max", + "shape": "plaintext", + "width": "3.6944" + } + ], + "edges": [ + { + "_gvid": 0, + "tail": 0, + "head": 1, + "_draw_": [ + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "b", + "points": [ + [ + 133.0, + 186.0 + ], + [ + 133.0, + 208.92 + ], + [ + 133.0, + 217.06 + ], + [ + 133.0, + 235.83 + ] + ] + } + ], + "_hdraw_": [ + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "C", + "grad": "none", + "color": "#000000" + }, + { + "op": "P", + "points": [ + [ + 129.5, + 236.0 + ], + [ + 133.0, + 246.0 + ], + [ + 136.5, + 236.0 + ] + ] + } + ], + "arrowhead": "normal", + "color": "black", + "fontname": "arial", + "fontsize": "10", + "headport": "InputPort-InputPort-0", + "label": "", + "penwidth": "1", + "pos": "e,133,246 133,186 133,208.92 133,217.06 133,235.83", + "tailport": "OutputPort-OutputPort-0" + }, + { + "_gvid": 1, + "tail": 1, + "head": 2, + "_draw_": [ + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "b", + "points": [ + [ + 133.0, + 396.0 + ], + [ + 133.0, + 418.92 + ], + [ + 133.0, + 427.06 + ], + [ + 133.0, + 445.83 + ] + ] + } + ], + "_hdraw_": [ + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "C", + "grad": "none", + "color": "#000000" + }, + { + "op": "P", + "points": [ + [ + 129.5, + 446.0 + ], + [ + 133.0, + 456.0 + ], + [ + 136.5, + 446.0 + ] + ] + } + ], + "arrowhead": "normal", + "color": "black", + "fontname": "arial", + "fontsize": "10", + "headport": "InputPort-InputPort-0", + "label": "", + "penwidth": "1", + "pos": "e,133,456 133,396 133,418.92 133,427.06 133,445.83", + "tailport": "OutputPort-OutputPort-0" + } + ] +} \ No newline at end of file diff --git a/src/resources/single_nodes.json b/src/resources/single_nodes.json new file mode 100644 index 00000000..cc54c58b --- /dev/null +++ b/src/resources/single_nodes.json @@ -0,0 +1,592 @@ +{ + "name": "single_nodes", + "directed": true, + "strict": false, + "_draw_": [ + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#ffffff" + }, + { + "op": "P", + "points": [ + [ + 0.0, + 0.0 + ], + [ + 0.0, + 200.82 + ], + [ + 251.73, + 200.82 + ], + [ + 251.73, + 0.0 + ] + ] + } + ], + "bb": "0,0,251.73,200.82", + "xdotversion": "1.7", + "_subgraph_cnt": 0, + "objects": [ + { + "_gvid": 0, + "name": "single_node", + "_draw_": [ + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "e", + "rect": [ + 125.87, + 100.41, + 125.73, + 100.32 + ] + } + ], + "_ldraw_": [ + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fffff0" + }, + { + "op": "P", + "points": [ + [ + 44.87, + 33.41 + ], + [ + 44.87, + 167.41 + ], + [ + 206.87, + 167.41 + ], + [ + 206.87, + 33.41 + ] + ] + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 48.87, + 132.41 + ], + [ + 48.87, + 163.41 + ], + [ + 202.87, + 163.41 + ], + [ + 202.87, + 132.41 + ] + ] + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 52.87, + 136.41 + ], + [ + 52.87, + 159.41 + ], + [ + 198.87, + 159.41 + ], + [ + 198.87, + 136.41 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 53.87, + 137.41 + ], + [ + 53.87, + 158.41 + ], + [ + 197.87, + 158.41 + ], + [ + 197.87, + 137.41 + ] + ] + }, + { + "op": "F", + "size": 14.0, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [ + 85.87, + 145.21 + ], + "align": "l", + "width": 80.0, + "text": "OutputPort-0" + }, + { + "op": "F", + "size": 16.0, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 48.87, + 97.61 + ], + "align": "l", + "width": 81.0, + "text": "single_node" + }, + { + "op": "S", + "style": "setlinewidth(1)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 134.87, + 73.41 + ], + [ + 134.87, + 127.41 + ], + [ + 202.87, + 127.41 + ], + [ + 202.87, + 73.41 + ] + ] + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 138.87, + 77.41 + ], + [ + 138.87, + 123.41 + ], + [ + 198.87, + 123.41 + ], + [ + 198.87, + 77.41 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 139.87, + 101.41 + ], + [ + 139.87, + 122.41 + ], + [ + 197.87, + 122.41 + ], + [ + 197.87, + 101.41 + ] + ] + }, + { + "op": "F", + "size": 14.0, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 142.87, + 109.21 + ], + "align": "l", + "width": 52.0, + "text": "intercept" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 139.87, + 78.41 + ], + [ + 139.87, + 99.41 + ], + [ + 197.87, + 99.41 + ], + [ + 197.87, + 78.41 + ] + ] + }, + { + "op": "F", + "size": 14.0, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 153.87, + 86.21 + ], + "align": "l", + "width": 30.0, + "text": "slope" + }, + { + "op": "S", + "style": "setlinewidth(1)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#fafad0" + }, + { + "op": "P", + "points": [ + [ + 48.87, + 37.41 + ], + [ + 48.87, + 68.41 + ], + [ + 202.87, + 68.41 + ], + [ + 202.87, + 37.41 + ] + ] + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#eee8aa" + }, + { + "op": "P", + "points": [ + [ + 52.87, + 41.41 + ], + [ + 52.87, + 64.41 + ], + [ + 198.87, + 64.41 + ], + [ + 198.87, + 41.41 + ] + ] + }, + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#fafad2" + }, + { + "op": "p", + "points": [ + [ + 53.87, + 42.41 + ], + [ + 53.87, + 63.41 + ], + [ + 197.87, + 63.41 + ], + [ + 197.87, + 42.41 + ] + ] + }, + { + "op": "F", + "size": 14.0, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [ + 90.87, + 50.21 + ], + "align": "l", + "width": 70.0, + "text": "InputPort-0" + }, + { + "op": "S", + "style": "setlinewidth(1)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "p", + "points": [ + [ + 44.87, + 33.41 + ], + [ + 44.87, + 167.41 + ], + [ + 206.87, + 167.41 + ], + [ + 206.87, + 33.41 + ] + ] + } + ], + "height": "2.7891", + "label": "
OutputPort-0
single_node
intercept
slope
InputPort-0
", + "pos": "125.87,100.41", + "width": "3.4963" + } + ] +} \ No newline at end of file diff --git a/src/services/queryService.ts b/src/services/queryService.ts index 25de92c5..873351ac 100644 --- a/src/services/queryService.ts +++ b/src/services/queryService.ts @@ -16,6 +16,8 @@ export default class QueryService { return '[(InputPort InputPort-0), (ParameterPort intercept), (ParameterPort slope), (OutputPort OutputPort-0)]' case 'mid': return '[(InputPort InputPort-0), (ParameterPort intercept), (ParameterPort slope), (OutputPort OutputPort-0)]' + case 'mid2': + return '[(InputPort InputPort-0), (ParameterPort intercept), (ParameterPort slope), (OutputPort OutputPort-0)]' case 'output': return '[(InputPort InputPort-0), (ParameterPort intercept), (ParameterPort slope), (OutputPort OutputPort-0)]' case 'single_node': @@ -32,4 +34,3 @@ export default class QueryService { } } - From 13e18e4756f53954677b645a131333c6314c3c5c Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Sat, 15 Oct 2022 13:05:13 +0100 Subject: [PATCH 05/14] #36 moving logic from graph to children for metanodes --- src/components/Main.js | 17 +++- .../views/compositions/Composition.js | 8 +- .../views/mechanisms/MechMetadata.js | 20 +++- src/model/Interpreter.ts | 80 +++++++-------- .../nodes/composition/CompositionNode.ts | 83 ++++++++++++---- src/model/nodes/mechanism/MechanismNode.ts | 97 ++++++++++++------- 6 files changed, 207 insertions(+), 98 deletions(-) diff --git a/src/components/Main.js b/src/components/Main.js index ed489bf5..0a0cd0de 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -33,6 +33,17 @@ class Main extends React.Component { this.componentsMap.nodes.set(PNLClasses.COMPOSITION, Composition); this.componentsMap.nodes.set(PNLClasses.MECHANISM, GenericMechanism); this.componentsMap.links.set(PNLClasses.PROJECTION, CustomLinkWidget); + + this.metaCallback = this.metaCallback.bind(this); + } + + shouldComponentUpdate() { + return false; + } + + metaCallback(event) { + console.log('metacallback'); + console.log(event); } render() { @@ -40,7 +51,11 @@ class Main extends React.Component { return (
- {value} */} ) + + const updateOptionField = (label, value) => ( + + {label} + { + model.setOption(label, e.target.value) + }} + variant="outlined" + style={{ zIndex: 11 }} + /> + {/* {value} */} + + ) + return ( <> {options.selected && ( @@ -154,7 +172,7 @@ class MechMetadata extends React.Component { functionValues('Size', '8.90') } { - functionValues('Prefs', '44') + updateOptionField('variant', options.variant) } Function diff --git a/src/model/Interpreter.ts b/src/model/Interpreter.ts index b2c7857e..e3e46308 100644 --- a/src/model/Interpreter.ts +++ b/src/model/Interpreter.ts @@ -1,9 +1,9 @@ import { PNLClasses } from '../constants'; -import { MetaLink, MetaNode, PortTypes } from '@metacell/meta-diagram'; import ProjectionLink from './links/ProjectionLink'; import QueryService from '../services/queryService'; import MechanismNode from './nodes/mechanism/MechanismNode'; import CompositionNode from './nodes/composition/CompositionNode'; +import { MetaLink, MetaNode, PortTypes } from '@metacell/meta-diagram'; export default class ModelInterpreter { nativeModel: any; @@ -36,14 +36,14 @@ export default class ModelInterpreter { } _convertModel(model: any) : Object { + model[PNLClasses.MECHANISM].forEach((singleNode: any) => { + this.castMechanism(singleNode, undefined, this.modelMap); + }); model[PNLClasses.COMPOSITION].forEach((singleModel: any) => { this.nodeIdsMap = new Map(); this.linkIdsMap = new Map(); this.castComposition(singleModel, undefined, this.modelMap); }); - model[PNLClasses.MECHANISM].forEach((singleNode: any) => { - this.castMechanism(singleNode, undefined, this.modelMap); - }); this.setMetaModel() return this.pnlModel; } @@ -88,7 +88,6 @@ export default class ModelInterpreter { }; const result = QueryService.getPorts(name, type); - if (result !== '') { const parsedPorts = result.replace('[', '').replace(']', '').split(', '); parsedPorts.forEach(element => { @@ -113,7 +112,7 @@ export default class ModelInterpreter { item: MechanismNode|CompositionNode|ProjectionLink|any, parent: any|undefined, modelMap: { [key: string]: Map }) - : MechanismNode|CompositionNode|ProjectionLink { + : CompositionNode { let newNode = item; let extra: { [key: string]: any } = {}; let ports : any = []; @@ -125,12 +124,18 @@ export default class ModelInterpreter { }; if (item?.bb) { let _vertices = item.bb.split(','); - boundingBox.llx = _vertices[0]; - boundingBox.lly = _vertices[1]; - boundingBox.urx = _vertices[2]; - boundingBox.ury = _vertices[3]; + boundingBox.llx = parseFloat(_vertices[0]); + boundingBox.lly = parseFloat(_vertices[1]); + boundingBox.urx = parseFloat(_vertices[2]); + boundingBox.ury = parseFloat(_vertices[3]); + } + extra['boundingBox'] = boundingBox; + extra['position'] = { + x: boundingBox.llx, + y: boundingBox.lly } - newNode = new CompositionNode(item?.name, parent, '', false, ports, extra, undefined, boundingBox); + extra['isExpanded'] = false; + newNode = new CompositionNode(item?.name, parent, ports, extra); modelMap['nodes'].set(newNode.getName(), newNode); // temp array to host all the nested compositions let childrenCompositions: Array = []; @@ -143,6 +148,7 @@ export default class ModelInterpreter { childrenCompositions.push(child) } else { newChild = this.castMechanism(child, newNode, this.modelMap); + newChild.setParent(newNode); newNode.addChild(newChild); } if (newChild && !this.nodeIdsMap.has(child?._gvid)) { @@ -164,12 +170,11 @@ export default class ModelInterpreter { item.edges.forEach((edge: any) => { let tail = this.nodeIdsMap.get(edge.tail); let head = this.nodeIdsMap.get(edge.head); - // TODO: adjust the cast method below with tail and head let newChild = this.castEdge(edge, tail, head, newNode, this.modelMap); if (newChild && !this.linkIdsMap.has(edge?._gvid)) { this.linkIdsMap.set(edge?._gvid, newChild); } - newNode.addChild(newChild); + // newNode.addChild(newChild); }); this.pnlModel[PNLClasses.COMPOSITION].push(newNode); @@ -180,7 +185,7 @@ export default class ModelInterpreter { item: MechanismNode|CompositionNode|ProjectionLink|any, parent: CompositionNode, modelMap: { [key: string]: Map }) - : MechanismNode|CompositionNode|ProjectionLink { + : CompositionNode { let newNode = item; let extra: { [key: string]: any } = {}; let ports : any = []; @@ -192,12 +197,18 @@ export default class ModelInterpreter { }; if (item?.bb) { let _vertices = item.bb.split(','); - boundingBox.llx = _vertices[0]; - boundingBox.lly = _vertices[1]; - boundingBox.urx = _vertices[2]; - boundingBox.ury = _vertices[3]; + boundingBox.llx = parseFloat(_vertices[0]); + boundingBox.lly = parseFloat(_vertices[1]); + boundingBox.urx = parseFloat(_vertices[2]); + boundingBox.ury = parseFloat(_vertices[3]); + } + extra['boundingBox'] = boundingBox; + extra['position'] = { + x: boundingBox.llx, + y: boundingBox.lly } - newNode = new CompositionNode(item?.name, parent, '', false, ports, extra, undefined, boundingBox); + extra['isExpanded'] = false; + newNode = new CompositionNode(item?.name, parent, ports, extra); modelMap['nodes'].set(newNode.getName(), newNode); // Iterates nodes of the nested composition to fill the children map/array @@ -218,21 +229,17 @@ export default class ModelInterpreter { item: MechanismNode|CompositionNode|ProjectionLink|any, parent: CompositionNode|undefined, modelMap: { [key: string]: Map }) - : MechanismNode|CompositionNode|ProjectionLink { + : MechanismNode { let newNode = item; - let ports: { [key: string]: any } = this.parseNodePorts(item?.name, PNLClasses.MECHANISM); - let extra: { [key: string]: any } = {}; let coordinates = item.pos.split(','); - newNode = new MechanismNode( - item?.name, - parent, - '', - false, - ports, - extra, - coordinates[0], - coordinates[1], - ); + let ports: { [key: string]: any } = this.parseNodePorts(item?.name, PNLClasses.MECHANISM); + let extra: { [key: string]: any } = { + position: { + x: parseFloat(coordinates[0]), + y: parseFloat(coordinates[1]) + } + }; + newNode = new MechanismNode(item?.name, parent, ports, extra,); modelMap['nodes'].set(newNode.getName(), newNode); this.pnlModel[PNLClasses.MECHANISM].push(newNode); return newNode; @@ -244,13 +251,11 @@ export default class ModelInterpreter { receiver: MechanismNode|CompositionNode, parent: CompositionNode|undefined, modelMap: { [key: string]: Map }) - : MechanismNode|CompositionNode|ProjectionLink { + : ProjectionLink { let newNode = item; let name = ''; let extra: { [key: string]: any } = {}; let senderPortName, receiverPortName; - // senderPortName = this.getPortName(item.tailport); - // receiverPortName = this.getPortName(item.headport); senderPortName = item.tailport; receiverPortName = item.headport; newNode = new ProjectionLink( @@ -266,9 +271,4 @@ export default class ModelInterpreter { this.pnlModel[PNLClasses.PROJECTION].push(newNode); return newNode; } - - getPortName(portString: string): string { - const [portType, ...portName] = portString.split('-'); - return portName.join('-'); - } } diff --git a/src/model/nodes/composition/CompositionNode.ts b/src/model/nodes/composition/CompositionNode.ts index 20574da6..6c68e196 100644 --- a/src/model/nodes/composition/CompositionNode.ts +++ b/src/model/nodes/composition/CompositionNode.ts @@ -1,26 +1,22 @@ import { PNLClasses } from '../../../constants'; -import ModelInterpreter from '../../Interpreter'; import MechanismNode from '../mechanism/MechanismNode'; import ProjectionLink from '../../links/ProjectionLink'; -import { MetaNode, MetaPort, Position, PortTypes } from '@metacell/meta-diagram'; +import { MetaNode, MetaPort, Position } from '@metacell/meta-diagram'; +import { ExtraObject } from '../utils'; export default class CompositionNode extends MechanismNode { children: {[key: string]: any}; + metaChildren: Array; childrenMap: Map; - innerClass: string; - boundingBox: {[key: string]: Number}|undefined; constructor( name: string, parent: CompositionNode|undefined, - icon?: string, - isExpaded?: boolean, ports?: { [key: string]: Array }, - extra?: Object, - children?: {[key: string]: any}, - boundingBox?: {[key: string]: Number}|undefined) + extra?: ExtraObject, + children?: {[key: string]: any}) { - super(name, parent, icon, isExpaded, ports, extra); + super(name, parent, ports, extra); this.childrenMap = new Map(); this.children = children !== undefined ? children : { @@ -28,20 +24,47 @@ export default class CompositionNode extends MechanismNode { [PNLClasses.PROJECTION]: [], [PNLClasses.COMPOSITION]: [], }; + + this.metaChildren = []; + if (children) { + children[PNLClasses.COMPOSITION].forEach((child: any) => { + if (this.childrenMap.has(child.getName())) { + throw Error('ChildrenMap already has an object with that name.'); + } + this.childrenMap.set(child.getName(), child); + this.metaChildren.push(child.getMetaNode()); + }); + children[PNLClasses.MECHANISM].forEach((child: any) => { + if (this.childrenMap.has(child.getName())) { + throw Error('ChildrenMap already has an object with that name.'); + } + this.childrenMap.set(child.getName(), child); + this.metaChildren.push(child.getMetaNode()); + }); + } + + if (this.extra?.boundingBox) { + this.extra.position = { + x: this.extra.boundingBox.llx + 75, + y: this.extra.boundingBox.lly + 75 + } + } + this.innerClass = PNLClasses.COMPOSITION; - this.boundingBox = boundingBox; } - addChild(child: MechanismNode|CompositionNode|ProjectionLink) { + addChild(child: MechanismNode|CompositionNode) { if (!this.childrenMap.has(child.getName())) { this.childrenMap.set(child.getName(), child); + this.metaChildren.push(child.getMetaNode()); } this.children[child.getType()].push(child); } - removeChild(child: MechanismNode|CompositionNode|ProjectionLink) { + removeChild(child: MechanismNode|CompositionNode) { if (this.childrenMap.has(child.getName())) { this.childrenMap.delete(child.getName()); + this.metaChildren = this.metaChildren.filter((item: MetaNode) => item.getId() !== child.getName()); } switch (child.getType()) { case PNLClasses.MECHANISM: { @@ -73,26 +96,48 @@ export default class CompositionNode extends MechanismNode { return this.innerClass; } + getPosition(): Position { + if (this.extra?.position === undefined) { + this.setPosition(Math.random() * 900, Math.random() * 900); + } + + return new Position( + // @ts-ignore + this.extra.position.x + 75, this.extra.position.y + 75 + ); + } + getMetaNode() : any { // TODO: get position from the graphviz data - let x = 200 + Math.random() * 600; - let y = 200 + Math.random() * 600; - let parent = this.parent ? this.parent.getMetaNode() : undefined; + // @ts-ignore + const width = Math.abs(parseFloat(this.extra.boundingBox['llx']) - parseFloat(this.extra.boundingBox['urx'])); + // @ts-ignore + const height = Math.abs(parseFloat(this.extra.boundingBox['ury']) - parseFloat(this.extra.boundingBox['lly'])); + const compositions = this.children[PNLClasses.COMPOSITION].map((child: CompositionNode) => { + return child.getMetaNode() + }); + const mechanisms = this.children[PNLClasses.MECHANISM].map((child: MechanismNode) => { + return child.getMetaNode() + }); + const family = [...compositions, ...mechanisms]; let ports: Array = [] return new MetaNode( this.name, this.name, PNLClasses.COMPOSITION, - new Position(x, y), + this.getPosition(), 'node-gray', - parent, + this.metaParent, ports, + family, new Map(Object.entries({ name: 'Mechanism Name', variant: 'node-gray', pnlClass: 'ProcessingMechanism', shape: 'circle', - selected: false + selected: false, + width: width, + height: height, }) ) ); diff --git a/src/model/nodes/mechanism/MechanismNode.ts b/src/model/nodes/mechanism/MechanismNode.ts index 45f29db5..084ac627 100644 --- a/src/model/nodes/mechanism/MechanismNode.ts +++ b/src/model/nodes/mechanism/MechanismNode.ts @@ -2,37 +2,28 @@ import { PNLClasses } from '../../../constants'; import IMetaDiagramConverter from '../IMetaDiagramConverter'; import CompositionNode from '../composition/CompositionNode'; import { MetaNode, MetaPort, Position, PortTypes } from '@metacell/meta-diagram'; +import { ExtraObject } from '../utils'; export default class MechanismNode implements IMetaDiagramConverter { name: string; - icon: string; - isExpanded: Boolean; ports: { [key: string]: Array }; - extra: Object; + extra: ExtraObject; innerClass: string; parent: CompositionNode|undefined; - xPosition: number; - yPosition: number; + metaParent: MetaNode|undefined; constructor( name: string, parent: CompositionNode|undefined, - icon?: string, - isExpaded?: boolean, ports?: { [key: string]: Array }, - extra?: Object, - xPosition?: number, - yPosition?: number) + extra?: ExtraObject | undefined) { this.name = name; - this.icon = icon !== undefined ? icon : ""; - this.ports = ports !== undefined ? ports : {}; - this.extra = extra !== undefined ? extra : []; - this.isExpanded = isExpaded !== undefined ? isExpaded : false; - this.innerClass = PNLClasses.MECHANISM; this.parent = parent; - this.xPosition = xPosition || Math.random() * 900; - this.yPosition = yPosition || Math.random() * 900; + this.metaParent = parent?.getMetaNode(); + this.innerClass = PNLClasses.MECHANISM; + this.extra = extra !== undefined ? extra : {}; + this.ports = ports !== undefined ? ports : {}; } getName() : string { @@ -40,20 +31,20 @@ export default class MechanismNode implements IMetaDiagramConverter { } setIcon(path: string) { - this.icon = path !== undefined ? path : this.icon; + this.extra.icon = path !== undefined ? path : this.extra.icon; } getIcon() : string{ - return this.icon; + return this?.extra?.icon || ''; } setExpanded(expandedState: Boolean) { - this.isExpanded = expandedState !== undefined ? expandedState : !this.isExpanded; + this.extra.isExpanded = expandedState !== undefined ? expandedState : !this.extra.isExpanded; } getExpanded() : Boolean { - return this.isExpanded; + return this?.extra?.isExpanded || false; } addPort(singlePort: any) { @@ -68,37 +59,77 @@ export default class MechanismNode implements IMetaDiagramConverter { return this.ports; } - setParent(newParent : CompositionNode) { + setParent(newParent: CompositionNode) { if (this.parent) { this.parent.removeChild(this); } this.parent = newParent; + this.metaParent = newParent.getMetaNode(); this.parent.addChild(this); } - getParent() : MechanismNode|CompositionNode|undefined { + getParent(): CompositionNode|undefined { return this.parent; } + getPosition(): Position { + if (this.extra?.position === undefined) { + this.extra.position = { + x: 0, + y: 0 + } + } + return new Position( + this.extra.position?.x, + this.extra.position?.y + ); + } + + setPosition(x:number, y:number) { + if (this.extra?.position === undefined) { + this.extra.position = { + x: 0, + y: 0 + } + } + this.extra.position.x = x; + this.extra.position.y = y; + } + getMetaNode() : MetaNode { - let parent = this.parent ? this.parent.getMetaNode() : undefined; let ports: Array = [] // TODO: the MetaPort has the enum prefix cause the projections are created with that prefix - // from the graphviz data we get. - this.ports[PortTypes.INPUT_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.INPUT_PORT + '-' + port, PortTypes.INPUT_PORT + '-' + port, PortTypes.INPUT_PORT, new Position(0, 0), new Map()))); - this.ports[PortTypes.OUTPUT_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.OUTPUT_PORT + '-' + port, PortTypes.OUTPUT_PORT + '-' + port, PortTypes.OUTPUT_PORT, new Position(0, 0), new Map()))); - this.ports[PortTypes.PARAMETER_PORT].forEach((port: any) => ports.push(new MetaPort(PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT + '-' + port, PortTypes.PARAMETER_PORT, new Position(0, 0), new Map()))); - // this.ports[PortTypes.INPUT_PORT].forEach((port: any) => ports.push(new MetaPort(port, port, PortTypes.INPUT_PORT, new Position(0, 0), new Map()))); - // this.ports[PortTypes.OUTPUT_PORT].forEach((port: any) => ports.push(new MetaPort(port, port, PortTypes.OUTPUT_PORT, new Position(0, 0), new Map()))); - // this.ports[PortTypes.PARAMETER_PORT].forEach((port: any) => ports.push(new MetaPort(port, port, PortTypes.PARAMETER_PORT, new Position(0, 0), new Map()))); + // to bring this to the attention of John or otherwise improve this in this codebase + this.ports[PortTypes.INPUT_PORT].forEach((port: any) => ports.push(new MetaPort( + PortTypes.INPUT_PORT + '-' + port, + PortTypes.INPUT_PORT + '-' + port, + PortTypes.INPUT_PORT, + new Position(0, 0), + new Map())) + ); + this.ports[PortTypes.OUTPUT_PORT].forEach((port: any) => ports.push(new MetaPort( + PortTypes.OUTPUT_PORT + '-' + port, + PortTypes.OUTPUT_PORT + '-' + port, + PortTypes.OUTPUT_PORT, + new Position(0, 0), + new Map())) + ); + this.ports[PortTypes.PARAMETER_PORT].forEach((port: any) => ports.push(new MetaPort( + PortTypes.PARAMETER_PORT + '-' + port, + PortTypes.PARAMETER_PORT + '-' + port, + PortTypes.PARAMETER_PORT, + new Position(0, 0), + new Map())) + ); return new MetaNode( this.name, this.name, PNLClasses.MECHANISM, - new Position(this.xPosition, this.yPosition), + this.getPosition(), 'node-gray', - parent, + this.metaParent, ports, + undefined, new Map(Object.entries({ name: 'Mechanism Name', variant: 'node-gray', From e216cf97ec380bd0cfd6e484f03adef4f2ffadd6 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Sat, 15 Oct 2022 13:05:43 +0100 Subject: [PATCH 06/14] #36 new interface for ExtraObject --- src/model/nodes/utils.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/model/nodes/utils.ts diff --git a/src/model/nodes/utils.ts b/src/model/nodes/utils.ts new file mode 100644 index 00000000..acbeae4a --- /dev/null +++ b/src/model/nodes/utils.ts @@ -0,0 +1,14 @@ +export interface ExtraObject { + position?: { + x: number; + y: number; + }, + boundingBox?: { + llx: number; + lly: number; + urx: number; + ury: number; + }, + isExpanded?: Boolean, + icon?: string +} \ No newline at end of file From 556b1eb5fa91cee718debd3488b6979a54be5750 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Tue, 25 Oct 2022 11:16:43 +0100 Subject: [PATCH 07/14] #36 handling parent child relationship and moving logic out of meta-diagram --- dev_setup.sh | 20 ----- installation.sh | 84 +++++++++++++++++++ src/App.js | 2 +- src/components/Main.js | 58 +++++++++++-- .../views/compositions/Composition.js | 1 + src/components/views/mechanisms/MechSimple.js | 2 +- src/model/Interpreter.ts | 53 +++++++++--- src/model/links/ProjectionLink.ts | 4 +- .../nodes/composition/CompositionNode.ts | 17 ++-- src/model/nodes/mechanism/MechanismNode.ts | 14 ++-- 10 files changed, 190 insertions(+), 65 deletions(-) delete mode 100755 dev_setup.sh create mode 100755 installation.sh diff --git a/dev_setup.sh b/dev_setup.sh deleted file mode 100755 index 51ed4924..00000000 --- a/dev_setup.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -npm -g install yalc -PSYVIEW=`pwd` - -if [ -d '../meta-diagram' ]; then - cd ../meta-diagram; - yarn && yarn run build:dev && yalc push --changed - cd $PSYVIEW -else - cd ../ - git clone https://github.com/metacell/meta-diagram - cd meta-diagram - yarn && yarn run build:dev && yalc push --changed - cd $PSYVIEW -fi - -yalc add @metacell/meta-diagram -yarn -yarn run start diff --git a/installation.sh b/installation.sh new file mode 100755 index 00000000..718b9693 --- /dev/null +++ b/installation.sh @@ -0,0 +1,84 @@ +#!/bin/bash + + +#!/bin/bash +set -e + +INSTALL=false +UPDATE=false + +function parse() { + for arg in "$@"; do # transform long options to short ones + shift + case "$arg" in + "--install") set -- "$@" "-n" ;; + "--update") set -- "$@" "-v" ;; + *) set -- "$@" "$arg" + esac + done + + while getopts "iu" optname + do + case "$optname" in + "i") INSTALL=true ;; + "u") UPDATE=true ;; + esac + done + shift "$((OPTIND-1))" # shift out all the already processed options +} + + +parse "$@" + +echo "=== Install / Update script for PsyNeuLinkViewer and meta-diagram ===" +#echo "Install is" +#echo $INSTALL +#echo "Update is" +#echo $UPDATE + + + +if [ "$INSTALL" = true ]; then + echo " ### re-installing all the packages" + npm -g install yalc + PSYVIEW=`pwd` + + if [ -d '../meta-diagram' ]; then + cd ../meta-diagram; + yarn && yarn run build:dev && yalc push --changed + cd $PSYVIEW + else + cd ../ + git clone https://github.com/metacell/meta-diagram + cd meta-diagram + yarn && yarn run build:dev && yalc push --changed + cd $PSYVIEW + fi + + yalc add @metacell/meta-diagram + rm -rf node_modules/ + yarn + yarn run start +elif [ "$UPDATE" = true ]; then + echo " ### Updating meta-diagram" + yarn remove @metacell/meta-diagram + PSYVIEW=`pwd` + + if [ -d '../meta-diagram' ]; then + cd ../meta-diagram; + yarn && yarn run build:dev && yalc push --changed + cd $PSYVIEW + else + cd ../ + git clone https://github.com/metacell/meta-diagram + cd meta-diagram + yarn && yarn run build:dev && yalc push --changed + cd $PSYVIEW + fi + yalc add @metacell/meta-diagram + yarn + yarn run start +else + echo " - The script can be run in update (-u / --update) or install (-i / --install) mode." + echo " - please use the option desidered to run the script again." +fi diff --git a/src/App.js b/src/App.js index 713b67bd..a1b8abba 100644 --- a/src/App.js +++ b/src/App.js @@ -3,7 +3,7 @@ import { ThemeProvider } from '@mui/material/styles'; import CssBaseline from '@mui/material/CssBaseline'; import theme from './theme'; import Layout from './components/common/Layout'; -import Loader from './components/common/Loader'; +// import Loader from './components/common/Loader'; function App() { return ( diff --git a/src/components/Main.js b/src/components/Main.js index 0a0cd0de..98bac309 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -1,13 +1,11 @@ import React from 'react'; -// import MechanismNode from '../model/nodes/mechanism/MechanismNode'; import { withStyles } from '@mui/styles'; import { PNLClasses } from '../constants'; -import { buildModel } from '../model/utils'; import BG from "../assets/svg/bg-dotted.svg"; import ModelInterpreter from '../model/Interpreter'; import Composition from './views/compositions/Composition'; import GenericMechanism from './views/mechanisms/GenericMechanism'; -import MetaDiagram, { ComponentsMap } from "@metacell/meta-diagram"; +import MetaDiagram, { CallbackTypes, ComponentsMap, EventTypes, Position } from "@metacell/meta-diagram"; import CustomLinkWidget from './views/projections/CustomLinkWidget'; const mockModel = require('../resources/model').mockModel; @@ -24,33 +22,75 @@ const styles = () => ({ class Main extends React.Component { constructor (props) { super(props); + // interpreter and model stored in the state will be moved to redux later this.state = {}; + + this.mousePos = {x: 0, y: 0}; this.interpreter = new ModelInterpreter(mockModel); this.model = this.interpreter.getModel(); this.metaModel = this.interpreter.getMetaModel(); + this.modelMap = this.interpreter.getModelElementsMap(); this.componentsMap = new ComponentsMap(new Map(), new Map()); - this.componentsMap.nodes.set(PNLClasses.COMPOSITION, Composition); this.componentsMap.nodes.set(PNLClasses.MECHANISM, GenericMechanism); this.componentsMap.links.set(PNLClasses.PROJECTION, CustomLinkWidget); + // functions bond to this scope this.metaCallback = this.metaCallback.bind(this); + 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) } - shouldComponentUpdate() { - return false; + handlePostUpdates(event) { + switch(event.function) { + case CallbackTypes.POSITION_CHANGED: { + this.interpreter.updateModel(event.entity); + break; + } + default: { + console.log('Function callback type not yet implemented ' + event.function); + break; + } + } + } + + handlePreUpdates(event) { + console.log('preUpdates not yet implemented.'); } metaCallback(event) { - console.log('metacallback'); - console.log(event); + switch (event.metaEvent) { + case EventTypes.PRE_UPDATE: { + this.handlePreUpdates(event); + break; + } + case EventTypes.POST_UPDATE: { + this.handlePostUpdates(event); + break; + } + default: { + throw Error('Unknown event type received from meta-diagram.'); + } + } + } + + mouseMoveCallback(event) { + this.mousePos.x = event.clientX; + this.mousePos.y = event.clientY; } render() { const { classes } = this.props; return ( -
+
{ + this.props.model.updateSize(ref.style.width, ref.style.height); this.setState({ width: ref.style.width, height: ref.style.height, diff --git a/src/components/views/mechanisms/MechSimple.js b/src/components/views/mechanisms/MechSimple.js index 4e89f126..3a5abcd4 100644 --- a/src/components/views/mechanisms/MechSimple.js +++ b/src/components/views/mechanisms/MechSimple.js @@ -36,7 +36,7 @@ class MechSimple extends React.Component { ); default: // TODO: what to do with other ports? - // console.log('different port found' + port.getName() + ' ' + port.getType()); + return (<>); } })} diff --git a/src/model/Interpreter.ts b/src/model/Interpreter.ts index e3e46308..8ba2007c 100644 --- a/src/model/Interpreter.ts +++ b/src/model/Interpreter.ts @@ -10,20 +10,27 @@ export default class ModelInterpreter { jsonModel: Object; modelMap: { [key: string]: Map }; pnlModel: { [key: string]: Array }; + metaModelMap: { [key: string]: Map }; metaModel: { [key: string]: Array }; nodeIdsMap: Map; linkIdsMap: Map; constructor(model: any) { this.modelMap = { - 'nodes': new Map(), - 'links': new Map() + [PNLClasses.COMPOSITION]: new Map(), + [PNLClasses.MECHANISM]: new Map(), + [PNLClasses.PROJECTION]: new Map(), }; this.pnlModel = { [PNLClasses.COMPOSITION]: [], [PNLClasses.MECHANISM]: [], [PNLClasses.PROJECTION]: [], }; + this.metaModelMap = { + [PNLClasses.COMPOSITION]: new Map(), + [PNLClasses.MECHANISM]: new Map(), + [PNLClasses.PROJECTION]: new Map(), + }; this.metaModel = { [PNLClasses.COMPOSITION]: [], [PNLClasses.MECHANISM]: [], @@ -48,10 +55,6 @@ export default class ModelInterpreter { return this.pnlModel; } - updateModel(newModel: any) { - this.jsonModel = this._convertModel(newModel); - } - getModel() { return this.jsonModel; } @@ -60,12 +63,29 @@ export default class ModelInterpreter { this.metaModel[PNLClasses.COMPOSITION] = this.pnlModel[PNLClasses.COMPOSITION].map( (item:CompositionNode) => item.getMetaNode() ); + this.metaModelMap[PNLClasses.COMPOSITION] = new Map( + this.metaModel[PNLClasses.COMPOSITION].map(object => { + return [object.getId(), object]; + }) + ); + this.metaModel[PNLClasses.MECHANISM] = this.pnlModel[PNLClasses.MECHANISM].map( (item:MechanismNode) => item.getMetaNode() ); + this.metaModelMap[PNLClasses.MECHANISM] = new Map( + this.metaModel[PNLClasses.MECHANISM].map(object => { + return [object.getId(), object]; + }) + ); + this.metaModel[PNLClasses.PROJECTION] = this.pnlModel[PNLClasses.PROJECTION].map( (item:ProjectionLink) => item.getMetaLink() ); + this.metaModelMap[PNLClasses.PROJECTION] = new Map( + this.metaModel[PNLClasses.PROJECTION].map(object => { + return [object.getId(), object]; + }) + ); } getMetaModel() { @@ -80,6 +100,13 @@ export default class ModelInterpreter { return this.modelMap; } + updateModel(item: MetaNode|MetaLink) { + // if (this.metaModelMap[item.getShape()].has(item.getId())) { + console.log('this is where I update the node'); + console.log(item); + // } + } + parseNodePorts(name: string, type: string): { [key: string]: any } { let ports: { [key: string]: any[] } = { [PortTypes.INPUT_PORT]: [], @@ -136,7 +163,7 @@ export default class ModelInterpreter { } extra['isExpanded'] = false; newNode = new CompositionNode(item?.name, parent, ports, extra); - modelMap['nodes'].set(newNode.getName(), newNode); + modelMap[PNLClasses.COMPOSITION].set(newNode.getName(), newNode); // temp array to host all the nested compositions let childrenCompositions: Array = []; @@ -147,7 +174,7 @@ export default class ModelInterpreter { // we park for now nested compositions childrenCompositions.push(child) } else { - newChild = this.castMechanism(child, newNode, this.modelMap); + newChild = this.castMechanism(child, newNode, modelMap); newChild.setParent(newNode); newNode.addChild(newChild); } @@ -159,7 +186,7 @@ export default class ModelInterpreter { // Now da we have all the mechanisms in the idsMap we continue with the compositions childrenCompositions.forEach((child: any) => { let newChild = undefined; - newChild = this.nestedComposition(child, newNode, this.modelMap); + newChild = this.nestedComposition(child, newNode, modelMap); newNode.addChild(newChild); if (newChild && !this.nodeIdsMap.has(child?._gvid)) { @@ -170,7 +197,7 @@ export default class ModelInterpreter { item.edges.forEach((edge: any) => { let tail = this.nodeIdsMap.get(edge.tail); let head = this.nodeIdsMap.get(edge.head); - let newChild = this.castEdge(edge, tail, head, newNode, this.modelMap); + let newChild = this.castEdge(edge, tail, head, newNode, modelMap); if (newChild && !this.linkIdsMap.has(edge?._gvid)) { this.linkIdsMap.set(edge?._gvid, newChild); } @@ -209,7 +236,7 @@ export default class ModelInterpreter { } extra['isExpanded'] = false; newNode = new CompositionNode(item?.name, parent, ports, extra); - modelMap['nodes'].set(newNode.getName(), newNode); + modelMap[PNLClasses.COMPOSITION].set(newNode.getName(), newNode); // Iterates nodes of the nested composition to fill the children map/array item.nodes.forEach((id: any) => { @@ -240,7 +267,7 @@ export default class ModelInterpreter { } }; newNode = new MechanismNode(item?.name, parent, ports, extra,); - modelMap['nodes'].set(newNode.getName(), newNode); + modelMap[PNLClasses.MECHANISM].set(newNode.getName(), newNode); this.pnlModel[PNLClasses.MECHANISM].push(newNode); return newNode; } @@ -267,7 +294,7 @@ export default class ModelInterpreter { false, extra ); - modelMap['links'].set(newNode.getName(), newNode); + modelMap[PNLClasses.PROJECTION].set(newNode.getName(), newNode); this.pnlModel[PNLClasses.PROJECTION].push(newNode); return newNode; } diff --git a/src/model/links/ProjectionLink.ts b/src/model/links/ProjectionLink.ts index 75c517a4..b8c5d417 100644 --- a/src/model/links/ProjectionLink.ts +++ b/src/model/links/ProjectionLink.ts @@ -64,7 +64,7 @@ export default class ProjectionLink implements IMetaLinkConverter { return new MetaLink( this.name, this.name, - 'projection', + PNLClasses.PROJECTION, this.sender, this.senderPort, this.receiver, @@ -72,7 +72,7 @@ export default class ProjectionLink implements IMetaLinkConverter { 'undefined', new Map( Object.entries({ - color: 'rgb(255,192,0)' + pnlClass: PNLClasses.PROJECTION, }) ) ); diff --git a/src/model/nodes/composition/CompositionNode.ts b/src/model/nodes/composition/CompositionNode.ts index 6c68e196..e94a091b 100644 --- a/src/model/nodes/composition/CompositionNode.ts +++ b/src/model/nodes/composition/CompositionNode.ts @@ -103,7 +103,7 @@ export default class CompositionNode extends MechanismNode { return new Position( // @ts-ignore - this.extra.position.x + 75, this.extra.position.y + 75 + this.extra.position.x, this.extra.position.y ); } @@ -113,13 +113,6 @@ export default class CompositionNode extends MechanismNode { const width = Math.abs(parseFloat(this.extra.boundingBox['llx']) - parseFloat(this.extra.boundingBox['urx'])); // @ts-ignore const height = Math.abs(parseFloat(this.extra.boundingBox['ury']) - parseFloat(this.extra.boundingBox['lly'])); - const compositions = this.children[PNLClasses.COMPOSITION].map((child: CompositionNode) => { - return child.getMetaNode() - }); - const mechanisms = this.children[PNLClasses.MECHANISM].map((child: MechanismNode) => { - return child.getMetaNode() - }); - const family = [...compositions, ...mechanisms]; let ports: Array = [] return new MetaNode( this.name, @@ -129,12 +122,12 @@ export default class CompositionNode extends MechanismNode { 'node-gray', this.metaParent, ports, - family, + this.metaChildren, new Map(Object.entries({ - name: 'Mechanism Name', + name: this.name, variant: 'node-gray', - pnlClass: 'ProcessingMechanism', - shape: 'circle', + pnlClass: PNLClasses.COMPOSITION, + shape: PNLClasses.COMPOSITION, selected: false, width: width, height: height, diff --git a/src/model/nodes/mechanism/MechanismNode.ts b/src/model/nodes/mechanism/MechanismNode.ts index 084ac627..c5a141aa 100644 --- a/src/model/nodes/mechanism/MechanismNode.ts +++ b/src/model/nodes/mechanism/MechanismNode.ts @@ -96,6 +96,10 @@ export default class MechanismNode implements IMetaDiagramConverter { this.extra.position.y = y; } + getType() : string { + return this.innerClass; + } + getMetaNode() : MetaNode { let ports: Array = [] // TODO: the MetaPort has the enum prefix cause the projections are created with that prefix @@ -131,17 +135,13 @@ export default class MechanismNode implements IMetaDiagramConverter { ports, undefined, new Map(Object.entries({ - name: 'Mechanism Name', + name: this.name, variant: 'node-gray', - pnlClass: 'ProcessingMechanism', - shape: 'circle', + pnlClass: PNLClasses.MECHANISM, + shape: PNLClasses.MECHANISM, selected: false }) ) ); } - - getType() : string { - return this.innerClass; - } } From 1e1b144a45d7fb863d5629d6d931bce2aa59a0b6 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Tue, 25 Oct 2022 15:34:01 +0100 Subject: [PATCH 08/14] #36 parent child relationship logic moved to application --- installation.sh | 5 - src/components/Main.js | 21 +- src/components/graph/MetaGraph.ts | 220 ++++++++++++++++++ .../views/compositions/Composition.js | 9 +- src/model/Interpreter.ts | 6 +- .../nodes/composition/CompositionNode.ts | 5 +- src/model/utils.js | 21 +- 7 files changed, 256 insertions(+), 31 deletions(-) create mode 100644 src/components/graph/MetaGraph.ts 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 +} From 256d0c820a088da608a182be31fd96f3e99e19f6 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Fri, 28 Oct 2022 09:57:18 +0100 Subject: [PATCH 09/14] #36 adding logic for the new path calculation --- installation.sh | 3 +- src/components/Main.js | 11 ++- src/components/graph/MetaGraph.ts | 90 ++++++++++++++++--- .../views/compositions/Composition.js | 28 +++--- 4 files changed, 104 insertions(+), 28 deletions(-) diff --git a/installation.sh b/installation.sh index 0cc3830b..f89ba787 100755 --- a/installation.sh +++ b/installation.sh @@ -56,7 +56,8 @@ if [ "$INSTALL" = true ]; then yarn run start elif [ "$UPDATE" = true ]; then echo " ### Updating meta-diagram" - yarn remove @metacell/meta-diagram + # yarn remove @metacell/meta-diagram + rm -rf yalc.lock PSYVIEW=`pwd` if [ -d '../meta-diagram' ]; then diff --git a/src/components/Main.js b/src/components/Main.js index f8975673..83a94211 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -48,10 +48,15 @@ class Main extends React.Component { } handlePostUpdates(event) { + const node = event.entity; switch(event.function) { case CallbackTypes.POSITION_CHANGED: { - const node = event.entity; - this.metaGraph.handleNodePositionChanged(node, this.mousePos.x, this.mousePos.y); + this.metaGraph.updateGraph(node, this.mousePos.x, this.mousePos.y); + this.interpreter.updateModel(node); + return true; + } + case CallbackTypes.CHILD_POSITION_CHANGED: { + this.metaGraph.handleNodePositionChanged(node); this.interpreter.updateModel(node); return true; } @@ -63,7 +68,7 @@ class Main extends React.Component { } handlePreUpdates(event) { - console.log('preUpdates not yet implemented.'); + // console.log('preUpdates not yet implemented.'); } metaCallback(event) { diff --git a/src/components/graph/MetaGraph.ts b/src/components/graph/MetaGraph.ts index 182dfd14..564d52b7 100644 --- a/src/components/graph/MetaGraph.ts +++ b/src/components/graph/MetaGraph.ts @@ -1,5 +1,6 @@ // import {MetaNodeModel} from "../react-diagrams/MetaNodeModel"; import {MetaLink, MetaNodeModel, MetaLinkModel} from "@metacell/meta-diagram" +import { PNLClasses } from "../../constants"; class Graph { private readonly node: MetaNodeModel; @@ -26,6 +27,10 @@ class Graph { this.children.set(graph.getID(), graph) } + deleteChild(id: string) : void { + this.children.delete(id); + } + getChildren(): MetaNodeModel[] { return Array.from(this.children.values()).map(g => g.getNode()) } @@ -60,10 +65,12 @@ class Graph { export class MetaGraph { private readonly roots: Map; private readonly links: MetaLinkModel[]; + private parentUpdating: boolean; constructor() { this.roots = new Map() this.links = []; + this.parentUpdating = false; } addLinks(links: MetaLink[]) { @@ -170,28 +177,82 @@ export class MetaGraph { return parent } - handleNodePositionChanged(metaNodeModel: MetaNodeModel, cursorX: number, cursorY: number){ + private findParentNodeGraph(path: string[]) : Graph { + const newPath = [...path]; + newPath.pop(); + return this.findNodeGraph(newPath); + } + + updateGraph(metaNodeModel: MetaNodeModel, cursorX: number, cursorY: number) { + this.updateNodeContainerBoundingBox(metaNodeModel); + let parent: MetaNodeModel|undefined = this.rootContainsNode(metaNodeModel, cursorX, cursorY); + let newPath = this.findNewPath(metaNodeModel, parent, cursorX, cursorY); + if (metaNodeModel.getGraphPath().join().toString() !== newPath.join().toString()) { + this.updateNodeInGraph(metaNodeModel, newPath); + } + this.handleNodePositionChanged(metaNodeModel); + } + + handleNodePositionChanged(metaNodeModel: MetaNodeModel){ // TODO: Update node parent (add or remove parent) // update node graph path, // bounding boxes of parents + // update the graph for right parent children relationship + this.updateNodeContainerBoundingBox(metaNodeModel); // 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)) { + rootContainsNode(metaNodeModel: MetaNodeModel, cursorX: number, cursorY: number): MetaNodeModel|undefined { + let parent = undefined + this.roots.forEach((graph, id) => { + const node = graph.getNode(); + if (node.getID() !== metaNodeModel.getID() + && node.getOption('shape') === PNLClasses.COMPOSITION + && node.getNodeBoundingBox().containsPoint(cursorX, cursorY)) + { parent = node; } }); - // TODO add the new child to the graph and update graphPath for the metaNodeModel instance + return parent; + } + + findNewPath(metaNodeModel: MetaNodeModel, parent: MetaNodeModel|undefined, cursorX: number, cursorY: number) { + let search: boolean = true; + let newPath: string[] = []; + while (search && parent) { + search = false; + const children = this.getChildren(parent); + // eslint-disable-next-line no-loop-func + children.forEach((child: MetaNodeModel) => { + if (!search + && child.getID() !== metaNodeModel.getID() + && child.getOption('shape') === PNLClasses.COMPOSITION + && child.getNodeBoundingBox().containsPoint(cursorX, cursorY)) + { + search = true; + parent = child; + } + }); + // @ts-ignore + newPath = parent.getGraphPath(); + } + return [...newPath, metaNodeModel.getID()]; + } + + updateNodeInGraph(metaNodeModel: MetaNodeModel, newPath: string[]) { + const oldPath = metaNodeModel.getGraphPath(); + if (oldPath.length === 1) { + this.roots.delete(oldPath[0]); + } else { + let parentGraph = this.findParentNodeGraph(oldPath); + parentGraph.deleteChild(metaNodeModel.getID()); + } + metaNodeModel.setOption('graphPath', newPath); + this.addNode(metaNodeModel); } private updateChildrenPosition(metaNodeModel: MetaNodeModel){ @@ -204,7 +265,7 @@ export class MetaGraph { */ // @ts-ignore const localPosition = n.getLocalPosition() - n.setPosition(metaNodeModel.getX() + localPosition.x, metaNodeModel.getY() + localPosition.y) + n.setNodePosition(metaNodeModel.getX() + localPosition.x, metaNodeModel.getY() + localPosition.y) }) } @@ -214,7 +275,12 @@ export class MetaGraph { metaNodeModel.updateLocalPosition(parent) } - updateNodesContainerBoundingBoxes(nodes: MetaNodeModel[]): void { - nodes.forEach(n => n.setContainerBoundingBox(this.getNodeContainerBoundingBox(n))) + updateNodeContainerBoundingBox(node: MetaNodeModel): void { + node.setContainerBoundingBox({ + left: node.getX(), + top: node.getY(), + bottom: node.getY() + node.height, + right: node.getX() + node.width + }); } } diff --git a/src/components/views/compositions/Composition.js b/src/components/views/compositions/Composition.js index 73a36719..8500b5a7 100644 --- a/src/components/views/compositions/Composition.js +++ b/src/components/views/compositions/Composition.js @@ -85,19 +85,9 @@ class Composition extends React.Component { height: props.model.options.height, } this.changeVisibility = this.changeVisibility.bind(this); - } - - changeVisibility() { - this.setState({expanded: !this.state.expanded}); - } - - render() { - const { expanded } = this.state; - const { classes } = this.props; - return ( - - { @@ -111,6 +101,20 @@ class Composition extends React.Component { > } label="New Comp" color="secondary" /> + ); + } + + changeVisibility() { + this.setState({expanded: !this.state.expanded}); + } + + render() { + const { expanded } = this.state; + const { classes } = this.props; + + return ( + + {this.rndElement} ); } From aacde89fb4d570432034820f6852732cf2885322 Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Fri, 28 Oct 2022 14:36:06 +0100 Subject: [PATCH 10/14] #36 workin on the child set position event propagation --- src/components/Main.js | 12 +++---- src/components/graph/MetaGraph.ts | 19 ++++++------ .../views/compositions/Composition.js | 31 +++++++++---------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/components/Main.js b/src/components/Main.js index 83a94211..c6f4a82a 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -51,12 +51,12 @@ class Main extends React.Component { const node = event.entity; switch(event.function) { case CallbackTypes.POSITION_CHANGED: { - this.metaGraph.updateGraph(node, this.mousePos.x, this.mousePos.y); - this.interpreter.updateModel(node); - return true; - } - case CallbackTypes.CHILD_POSITION_CHANGED: { - this.metaGraph.handleNodePositionChanged(node); + this.metaGraph.updateGraph( + node, + this.mousePos.x, + this.mousePos.y, + event?.extraCondition === CallbackTypes.CHILD_POSITION_CHANGED + ); this.interpreter.updateModel(node); return true; } diff --git a/src/components/graph/MetaGraph.ts b/src/components/graph/MetaGraph.ts index 564d52b7..c35c0062 100644 --- a/src/components/graph/MetaGraph.ts +++ b/src/components/graph/MetaGraph.ts @@ -1,5 +1,5 @@ // import {MetaNodeModel} from "../react-diagrams/MetaNodeModel"; -import {MetaLink, MetaNodeModel, MetaLinkModel} from "@metacell/meta-diagram" +import {MetaLink, MetaNodeModel, MetaLinkModel, CallbackTypes} from "@metacell/meta-diagram" import { PNLClasses } from "../../constants"; class Graph { @@ -183,12 +183,15 @@ export class MetaGraph { return this.findNodeGraph(newPath); } - updateGraph(metaNodeModel: MetaNodeModel, cursorX: number, cursorY: number) { + updateGraph(metaNodeModel: MetaNodeModel, cursorX: number, cursorY: number, isChild?: boolean) { + // update the graph for right parent children relationship this.updateNodeContainerBoundingBox(metaNodeModel); - let parent: MetaNodeModel|undefined = this.rootContainsNode(metaNodeModel, cursorX, cursorY); - let newPath = this.findNewPath(metaNodeModel, parent, cursorX, cursorY); - if (metaNodeModel.getGraphPath().join().toString() !== newPath.join().toString()) { - this.updateNodeInGraph(metaNodeModel, newPath); + if (!isChild) { + let parent: MetaNodeModel|undefined = this.rootContainsNode(metaNodeModel, cursorX, cursorY); + let newPath = this.findNewPath(metaNodeModel, parent, cursorX, cursorY); + if (metaNodeModel.getGraphPath().join().toString() !== newPath.join().toString()) { + this.updateNodeInGraph(metaNodeModel, newPath); + } } this.handleNodePositionChanged(metaNodeModel); } @@ -198,8 +201,6 @@ export class MetaGraph { // update node graph path, // bounding boxes of parents - // update the graph for right parent children relationship - this.updateNodeContainerBoundingBox(metaNodeModel); // Update children position (children should move the same delta as node) this.updateChildrenPosition(metaNodeModel) // Update local position / relative position to the parent @@ -265,7 +266,7 @@ export class MetaGraph { */ // @ts-ignore const localPosition = n.getLocalPosition() - n.setNodePosition(metaNodeModel.getX() + localPosition.x, metaNodeModel.getY() + localPosition.y) + n.setChildPosition(metaNodeModel.getX() + localPosition.x, metaNodeModel.getY() + localPosition.y) }) } diff --git a/src/components/views/compositions/Composition.js b/src/components/views/compositions/Composition.js index 8500b5a7..b917c594 100644 --- a/src/components/views/compositions/Composition.js +++ b/src/components/views/compositions/Composition.js @@ -85,9 +85,22 @@ class Composition extends React.Component { height: props.model.options.height, } this.changeVisibility = this.changeVisibility.bind(this); + } + + changeVisibility() { + this.setState({expanded: !this.state.expanded}); + } - this.rndElement = ( - + { @@ -101,20 +114,6 @@ class Composition extends React.Component { > } label="New Comp" color="secondary" /> - ); - } - - changeVisibility() { - this.setState({expanded: !this.state.expanded}); - } - - render() { - const { expanded } = this.state; - const { classes } = this.props; - - return ( - - {this.rndElement} ); } From 3580b5461fc77771013f7aec27210250d3378a6e Mon Sep 17 00:00:00 2001 From: Dario Del Piano Date: Fri, 28 Oct 2022 19:46:44 +0100 Subject: [PATCH 11/14] #36 sync parent children on position updates --- src/components/graph/MetaGraph.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/graph/MetaGraph.ts b/src/components/graph/MetaGraph.ts index c35c0062..5c425457 100644 --- a/src/components/graph/MetaGraph.ts +++ b/src/components/graph/MetaGraph.ts @@ -183,17 +183,21 @@ export class MetaGraph { return this.findNodeGraph(newPath); } - updateGraph(metaNodeModel: MetaNodeModel, cursorX: number, cursorY: number, isChild?: boolean) { + updateGraph(metaNodeModel: MetaNodeModel, cursorX: number, cursorY: number) { // update the graph for right parent children relationship this.updateNodeContainerBoundingBox(metaNodeModel); - if (!isChild) { + if (!this.parentUpdating) { + this.parentUpdating = true; let parent: MetaNodeModel|undefined = this.rootContainsNode(metaNodeModel, cursorX, cursorY); let newPath = this.findNewPath(metaNodeModel, parent, cursorX, cursorY); if (metaNodeModel.getGraphPath().join().toString() !== newPath.join().toString()) { this.updateNodeInGraph(metaNodeModel, newPath); } + this.handleNodePositionChanged(metaNodeModel); + this.parentUpdating = false; + } else { + this.handleNodePositionChanged(metaNodeModel); } - this.handleNodePositionChanged(metaNodeModel); } handleNodePositionChanged(metaNodeModel: MetaNodeModel){ @@ -266,7 +270,7 @@ export class MetaGraph { */ // @ts-ignore const localPosition = n.getLocalPosition() - n.setChildPosition(metaNodeModel.getX() + localPosition.x, metaNodeModel.getY() + localPosition.y) + n.setPosition(metaNodeModel.getX() + localPosition.x, metaNodeModel.getY() + localPosition.y) }) } @@ -277,7 +281,7 @@ export class MetaGraph { } updateNodeContainerBoundingBox(node: MetaNodeModel): void { - node.setContainerBoundingBox({ + node.setNodeBoundingBox({ left: node.getX(), top: node.getY(), bottom: node.getY() + node.height, From 454437647c2de450806999ce8f6eef6aaa41397c Mon Sep 17 00:00:00 2001 From: emekauja Date: Wed, 2 Nov 2022 14:59:31 +0100 Subject: [PATCH 12/14] #46 added sidebar nodes --- src/components/Main.js | 28 ++++--- src/components/views/sidebar/icons.js | 106 ++++++++++++++++++++++++++ src/components/views/sidebar/nodes.js | 48 ++++++++++++ 3 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 src/components/views/sidebar/icons.js create mode 100644 src/components/views/sidebar/nodes.js diff --git a/src/components/Main.js b/src/components/Main.js index c13de880..14c2dd89 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -1,14 +1,15 @@ import React from 'react'; // import MechanismNode from '../model/nodes/mechanism/MechanismNode'; import { withStyles } from '@mui/styles'; -import BG from "../assets/svg/bg-dotted.svg"; +import BG from '../assets/svg/bg-dotted.svg'; import ModelInterpreter from '../model/Interpreter'; -import MetaDiagram, { ComponentsMap } from "@metacell/meta-diagram"; +import MetaDiagram, { ComponentsMap } from '@metacell/meta-diagram'; import CustomLinkWidget from './views/projections/CustomLinkWidget'; import GenericMechanism from './views/mechanisms/GenericMechanism'; import { buildModel } from '../model/utils'; import { PNLClasses } from '../constants'; import CompositionDrawer from './views/CompositionDrawer'; +import { sideBarNodes } from './views/sidebar/nodes'; const mockModel = require('../resources/model').mockModel; @@ -18,30 +19,34 @@ const styles = () => ({ width: '100%', }, canvasBG: { - backgroundImage: `url(${BG})` - } + backgroundImage: `url(${BG})`, + }, }); class Main extends React.Component { - constructor (props) { + constructor(props) { super(props); this.state = {}; } render() { const { classes } = this.props; - const interpreter = new ModelInterpreter(mockModel); + const interpreter = new ModelInterpreter(mockModel); const model = interpreter.getModel(); const metaModel = buildModel(model); const componentsMap = new ComponentsMap( - new Map(Object.entries({'mechanism': GenericMechanism})), - new Map(Object.entries({'projection': CustomLinkWidget})) - ) + new Map(Object.entries({ mechanism: GenericMechanism })), + new Map(Object.entries({ projection: CustomLinkWidget })) + ); return (
- ( + + + +); + +export const TransferIcon = (props) => ( + + + +); + +export const ProcessingIcon = (props) => ( + + + +); + +export const IntegratorIcon = (props) => ( + + + +); + +export const ModulatoryIcon = (props) => ( + + + +); + +export const LearningIcon = (props) => ( + + + +); + +export const ControlIcon = (props) => ( + + + +); diff --git a/src/components/views/sidebar/nodes.js b/src/components/views/sidebar/nodes.js new file mode 100644 index 00000000..d4f53321 --- /dev/null +++ b/src/components/views/sidebar/nodes.js @@ -0,0 +1,48 @@ +import { + ControlIcon, + IntegratorIcon, + LearningIcon, + ModulatoryIcon, + ProcessingIcon, + TargetIcon, + TransferIcon, +} from './icons'; + +export const sideBarNodes = [ + { + id: 'targetMechanism', + name: 'Target', + icon: , + }, + { + id: 'targetMechanism', + name: 'Transfer', + icon: , + }, + { + id: 'processingMechanism', + name: 'Processing Mechanism', + icon: , + }, + { + id: 'integratorMechanism', + + name: 'Integrator', + icon: , + }, + { + id: 'modulatoryMechanism', + name: 'Modulatory Mechanism', + icon: , + }, + { + id: 'learningMechanism', + name: 'Learning Mechanism', + icon: , + }, + { + id: 'controlMechanism', + name: 'Control Mechanism', + icon: , + }, +]; From f057053a4baebc714938c7dfe59df671337c13f5 Mon Sep 17 00:00:00 2001 From: emekauja Date: Thu, 3 Nov 2022 16:09:34 +0100 Subject: [PATCH 13/14] #46 fix typos on sidebar nodes data --- src/components/views/sidebar/nodes.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/sidebar/nodes.js b/src/components/views/sidebar/nodes.js index d4f53321..706b8817 100644 --- a/src/components/views/sidebar/nodes.js +++ b/src/components/views/sidebar/nodes.js @@ -11,12 +11,12 @@ import { export const sideBarNodes = [ { id: 'targetMechanism', - name: 'Target', + name: 'Target Mechanism', icon: , }, { - id: 'targetMechanism', - name: 'Transfer', + id: 'transferMechanism', + name: 'Transfer Mechanism', icon: , }, { @@ -27,7 +27,7 @@ export const sideBarNodes = [ { id: 'integratorMechanism', - name: 'Integrator', + name: 'Integrator Mechanism', icon: , }, { From 5d1a255010cc029e43b41b37efbdb9b1711b4921 Mon Sep 17 00:00:00 2001 From: emekauja Date: Mon, 7 Nov 2022 15:54:38 +0100 Subject: [PATCH 14/14] #46 solve occurence of target from meta-diagram --- src/components/Main.js | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/components/Main.js b/src/components/Main.js index 9550ed57..eef88f5e 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -1,11 +1,16 @@ import React from 'react'; import { withStyles } from '@mui/styles'; import { PNLClasses } from '../constants'; -import BG from "../assets/svg/bg-dotted.svg"; +import BG from '../assets/svg/bg-dotted.svg'; import ModelInterpreter from '../model/Interpreter'; import Composition from './views/compositions/Composition'; import GenericMechanism from './views/mechanisms/GenericMechanism'; -import MetaDiagram, { CallbackTypes, ComponentsMap, EventTypes, Position } from "@metacell/meta-diagram"; +import MetaDiagram, { + CallbackTypes, + ComponentsMap, + EventTypes, + Position, +} from '@metacell/meta-diagram'; import CustomLinkWidget from './views/projections/CustomLinkWidget'; import { generateMetaGraph } from '../model/utils'; import CompositionDrawer from './views/CompositionDrawer'; @@ -13,7 +18,6 @@ import { sideBarNodes } from './views/sidebar/nodes'; const mockModel = require('../resources/model').mockModel; - const styles = () => ({ root: { height: 'calc(100vh - 3.5rem)', @@ -30,7 +34,7 @@ class Main extends React.Component { // interpreter and model stored in the state will be moved to redux later this.state = {}; - this.mousePos = {x: 0, y: 0}; + this.mousePos = { x: 0, y: 0 }; this.interpreter = new ModelInterpreter(mockModel); this.model = this.interpreter.getModel(); this.metaModel = this.interpreter.getMetaModel(); @@ -46,13 +50,16 @@ class Main extends React.Component { this.handlePostUpdates = this.handlePostUpdates.bind(this); this.mouseMoveCallback = this.mouseMoveCallback.bind(this); - this.metaGraph = generateMetaGraph([...this.metaModel[PNLClasses.COMPOSITION], ...this.metaModel[PNLClasses.MECHANISM]]); + this.metaGraph = generateMetaGraph([ + ...this.metaModel[PNLClasses.COMPOSITION], + ...this.metaModel[PNLClasses.MECHANISM], + ]); this.metaGraph.addLinks(this.metaModel[PNLClasses.PROJECTION]); } handlePostUpdates(event) { const node = event.entity; - switch(event.function) { + switch (event.function) { case CallbackTypes.POSITION_CHANGED: { this.metaGraph.updateGraph( node, @@ -64,7 +71,9 @@ class Main extends React.Component { return true; } default: { - console.log('Function callback type not yet implemented ' + event.function); + console.log( + 'Function callback type not yet implemented ' + event.function + ); return false; } } @@ -95,9 +104,17 @@ class Main extends React.Component { this.mousePos.y = event.clientY; } + updateSelectedBar(id) { + this.setState({ + selectedBarNode: id, + }); + } + render() { const { classes } = this.props; + console.log(sideBarNodes, 'sidebarNodes'); + return (