From f1a9fdc1a9188f6c80c1636a72b60ba0723f0f78 Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Mon, 23 Sep 2024 08:29:21 +0200 Subject: [PATCH 1/3] change sorting --- src/composables/LayoutDrawCycle.ts | 128 +++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 33 deletions(-) diff --git a/src/composables/LayoutDrawCycle.ts b/src/composables/LayoutDrawCycle.ts index 94e8ac6..9580dfc 100644 --- a/src/composables/LayoutDrawCycle.ts +++ b/src/composables/LayoutDrawCycle.ts @@ -76,6 +76,12 @@ import { emit } from "process"; * * 2. Utilitary functions for cycle coordinate * + * -> scoreSortingCycleForDrawing : + * calculate score for sorting function to know order of cycle drawing. + * + * -> calculateAllScoresForSubgraphs : + * Calculate the scores for all subgraphs in the network. + * * -> sortingCycleForDrawing : * Sorting function for knowing order of cycle drawing. * @@ -146,7 +152,8 @@ export async function coordinateAllCycles(subgraphNetwork:SubgraphNetwork,allowI const parentCycles = cycles.filter(cycle => !cycle.parentSubgraph || cycle.parentSubgraph.type !== TypeSubgraph.CYCLE); if (parentCycles.length === 0) throw new Error("No cycle found without a parent subgraph of type cycle : review implementation"); - parentCycles.sort( (a, b) => sortingCycleForDrawing(subgraphNetwork,a,b,true)); + const scores = await calculateAllScoresForSubgraphs(subgraphNetwork, parentCycles, true); + parentCycles.sort( (a, b) => sortingCycleForDrawing(scores[a.name],scores[b.name],true)); const largestParentCycle = parentCycles[0]; // get largest cycle cycleGroups[groupName].nodes.push(largestParentCycle.name); // add it to the current group of cycle @@ -173,7 +180,8 @@ export async function coordinateAllCycles(subgraphNetwork:SubgraphNetwork,allowI while (remainingCycles.length > 0 ) { // sort cycles by number of fixed node (and then by size) - remainingCycles.sort((a, b) => sortingCycleForDrawing(subgraphNetwork,a,b,newGroup)); + const scores = await calculateAllScoresForSubgraphs(subgraphNetwork, remainingCycles, newGroup); + remainingCycles.sort( (a, b) => sortingCycleForDrawing(scores[a.name],scores[b.name],newGroup)); const cycleToDraw = remainingCycles[0]; // the cycle with the most fixed nodes (or constraints) // if groupcycle do not exist : add one @@ -733,47 +741,101 @@ async function nodeMedianX(subgraphNetwork: SubgraphNetwork, listNodes: string[] /*******************************************************************************************************************************************************/ //___________________________________________________2. Utilitary functions for cycle coordinate ________________________________________________________________ +/** + * Score a subgraph for sorting. + * First get number of circle fixed nodes (nodes fixed in a circle drawing), then size, + * then if full counstraint is true : number of parent nodes (of the cycle), and finally number of child nodes (of the cycle). + * @param subgraphNetwork - The subgraph network. + * @param subgraph - The first cycle to compare. + * @param fullConstraint - A flag indicating whether to consider the full constraint (default is false). + * @returns The different value calculated. + */ + async function scoreSortingCycleForDrawing(subgraphNetwork:SubgraphNetwork,subgraph:Subgraph,fullConstraint:boolean=false):Promise<{fixedNodes:number,size:number,numberParent?:number,numberChild?:number}>{ + const network=subgraphNetwork.network; + + // number of fixed nodes + const fixedNodesA = subgraph.nodes.filter(node => network.nodes[node].metadataLayout && network.nodes[node].metadataLayout.fixedInCircle).length; + // size + const size= subgraph.nodes.length; + + if (fullConstraint){ + const numberParent = await parentNodeNotInCycle(subgraphNetwork, subgraph.nodes).flat().length; + const numberChild = await childNodeNotInCycle(subgraphNetwork, subgraph.nodes).flat().length; + return {fixedNodes:fixedNodesA,size:size,numberParent:numberParent,numberChild:numberChild}; + }else{ + return {fixedNodes:fixedNodesA,size:size}; + } +} + +/** + * Calculates scores for all subgraphs in the given subgraphNetwork. + * + * @param subgraphNetwork - The network of subgraphs to calculate scores for. + * @param subgraphs - An array of subgraphs to calculate scores for. + * @param fullConstraint - A boolean indicating whether to apply full constraints during score calculation. Defaults to false. + * @returns A promise that resolves to an object where each key is a subgraph name and the value is an object containing: + * - `fixedNodes`: The number of fixed nodes in the subgraph. + * - `size`: The size of the subgraph. + * - `numberParent` (if fullConstraint): The number of parent nodes in the subgraph. + * - `numberChild` (if fullConstraint): The number of child nodes in the subgraph. + */ +async function calculateAllScoresForSubgraphs( + subgraphNetwork: SubgraphNetwork, + subgraphs: Array<Subgraph>, + fullConstraint: boolean = false +): Promise<{ [key: string]: { fixedNodes: number, size: number, numberParent?: number, numberChild?: number } }> { + + const scores = await Promise.all(subgraphs.map(async (subgraph: Subgraph) => { + const score = await scoreSortingCycleForDrawing(subgraphNetwork, subgraph, fullConstraint); + return { [subgraph.name]: score }; + })); + + return scores.reduce((acc, score) => ({ ...acc, ...score }), {}); +} + /** * Sorting function for knowing order of cycle drawing. * First sort by number of circle fixed nodes (nodes fixed in a circle drawing), then by size, * then if full counstraint is true : by number of parent nodes (of the cycle), and finally by number of child nodes (of the cycle). - * @param subgraphNetwork - The subgraph network. * @param a - The first cycle to compare. * @param b - The second cycle to compare. * @param fullConstraint - A flag indicating whether to consider the full constraint (default is false). * @returns A number indicating the sorting order. */ - function sortingCycleForDrawing(subgraphNetwork:SubgraphNetwork,a:Subgraph,b:Subgraph,fullConstraint:boolean=false):number{ - const network=subgraphNetwork.network; - - // first sort by number of fixed nodes - const fixedNodesA = a.nodes.filter(node => network.nodes[node].metadataLayout && network.nodes[node].metadataLayout.fixedInCircle).length; - const fixedNodesB = b.nodes.filter(node => network.nodes[node].metadataLayout && network.nodes[node].metadataLayout.fixedInCircle).length; - if (fixedNodesA !== fixedNodesB){ - return fixedNodesB - fixedNodesA; - }else{ - // sort by size - if ( !fullConstraint || b.nodes.length !== a.nodes.length ){ - return b.nodes.length - a.nodes.length; - }else{ - // then by number of parent nodes - const totalParentNodesA = parentNodeNotInCycle(subgraphNetwork, a.nodes) - .flat().length; - const totalParentNodesB = parentNodeNotInCycle(subgraphNetwork, b.nodes) - .flat().length; - if (totalParentNodesA !== totalParentNodesB){ - return totalParentNodesB - totalParentNodesA; - }else{ - // then by number of child nodes - const totalChildNodesA = childNodeNotInCycle(subgraphNetwork, a.nodes) - .flat().length; - const totalChildNodesB = childNodeNotInCycle(subgraphNetwork, b.nodes) - .flat().length; - - return totalChildNodesB - totalChildNodesA; +function sortingCycleForDrawing(aScore:{fixedNodes:number,size:number,numberParent?:number,numberChild?:number}, + bScore:{fixedNodes:number,size:number,numberParent?:number,numberChild?:number}, + fullConstraint:boolean=false):number{ + if (!aScore || !bScore) { + throw new Error("score of subgraph cycle group must be defined"); + } + + // fixedNodes + if (aScore.fixedNodes !== bScore.fixedNodes) { + return bScore.fixedNodes - aScore.fixedNodes; + } + + //size + if ( !fullConstraint || aScore.size !== bScore.size) { + return bScore.size - aScore.size; + } + + // if fullConstraint + if (fullConstraint) { + // numberParent + if (aScore.numberParent==undefined || bScore.numberParent==undefined) throw new Error("numberParent must be defined if full constraint is true"); + if (aScore.numberParent !== bScore.numberParent) { + return (bScore.numberParent || 0) - (aScore.numberParent || 0); } - } - } + + // numberChild + if (aScore.numberChild==undefined || bScore.numberChild==undefined) throw new Error("numberChild must be defined if full constraint is true"); + if (aScore.numberChild !== bScore.numberChild) { + return (bScore.numberChild || 0) - (aScore.numberChild || 0); + } + } + + return 0; + } -- GitLab From f429be3d2356d3e17fc526c586b1dc279f44153a Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Mon, 23 Sep 2024 09:08:50 +0200 Subject: [PATCH 2/3] adding async for findTopNode, and so everywhere else + correction of tests with async --- src/composables/CalculateRelationCycle.ts | 51 ++++++------- src/composables/ConvertFromNetwork.ts | 12 +-- src/composables/LayoutDrawCycle.ts | 8 +- src/composables/LayoutSugiyama.ts | 2 +- .../__tests__/CalculateRelationCycle.test.ts | 75 +++++++++---------- .../__tests__/ConvertFromNetwork.test.ts | 40 +++++----- .../__tests__/LayoutSugiyama.test.ts | 2 +- 7 files changed, 95 insertions(+), 95 deletions(-) diff --git a/src/composables/CalculateRelationCycle.ts b/src/composables/CalculateRelationCycle.ts index f68a09a..6f97b6c 100644 --- a/src/composables/CalculateRelationCycle.ts +++ b/src/composables/CalculateRelationCycle.ts @@ -89,15 +89,15 @@ import { inCycle } from "./GetSetAttributsNodes"; * @param parentOrChild - Specifies whether to retrieve the parent cycles or the child of the cycle group. Can be either "parent" or "child". * @param xSort - Optional. Specifies whether to sort the nodes of the group cycle and their parent/child by their x position. Defaults to true. * The neighboring nodes are added in the order of the x position of the nodes inside the group cycle. - * @returns An array of cycle IDs representing the neighboring cycles. + * @returns Promise of an array of cycle IDs representing the neighboring cycles. */ -export function neighborsGroupCycle(subgraphNetwork:SubgraphNetwork,cycleGroupId:string, parentOrChild:"parent"|"child",xSort:boolean=true):string[]{ +export async function neighborsGroupCycle(subgraphNetwork:SubgraphNetwork,cycleGroupId:string, parentOrChild:"parent"|"child",xSort:boolean=true):Promise<string[]>{ if (subgraphNetwork[TypeSubgraph.CYCLEGROUP] && cycleGroupId in subgraphNetwork[TypeSubgraph.CYCLEGROUP]){ const cycleGroup=subgraphNetwork[TypeSubgraph.CYCLEGROUP][cycleGroupId]; if (cycleGroup.precalculatedNodesPosition){ const positionNodesCycleGroup=cycleGroup.precalculatedNodesPosition; // get the id of nodes in group cycle - const nodes=getNodesIDPlacedInGroupCycle(subgraphNetwork,cycleGroupId); + const nodes=await getNodesIDPlacedInGroupCycle(subgraphNetwork,cycleGroupId); // sort nodes of the group cycle by x if (xSort){ nodes.sort((nodeIdA, nodeIdB) => { @@ -109,12 +109,12 @@ export function neighborsGroupCycle(subgraphNetwork:SubgraphNetwork,cycleGroupId } if (parentOrChild==="parent"){ // get parent nodes - const parentsDoubleArray = parentNodeNotInCycle(subgraphNetwork, nodes,xSort); + const parentsDoubleArray = await parentNodeNotInCycle(subgraphNetwork, nodes,xSort); const parentCycles = Array.from(new Set(parentsDoubleArray.flat())); return parentCycles; } else { // get child nodes - const childrenDoubleArray = childNodeNotInCycle(subgraphNetwork, nodes,xSort); + const childrenDoubleArray = await childNodeNotInCycle(subgraphNetwork, nodes,xSort); const childCycles = Array.from(new Set(childrenDoubleArray.flat())); return childCycles; } @@ -133,9 +133,9 @@ export function neighborsGroupCycle(subgraphNetwork:SubgraphNetwork,cycleGroupId * @param subgraphNetwork - The subgraph network object. * @param listNodes - The list of nodes to check for parent nodes. * @param sort - Optional. Specifies whether to sort the parent nodes (for each node) by their x position. Defaults to false. - * @returns An array of arrays : each array are the parents of a node in the lists + * @returns Promise of an array of arrays : each array are the parents of a node in the lists */ -export function parentNodeNotInCycle(subgraphNetwork: SubgraphNetwork, listNodes: string[],sort:boolean=false): string[][] { +export async function parentNodeNotInCycle(subgraphNetwork: SubgraphNetwork, listNodes: string[],sort:boolean=false): Promise<string[][]> { const parentNodes = listNodes.map((node: string) => { let parentNodesI = subgraphNetwork.network.links .filter(link => link.target.id === node) // get link with those node as child @@ -160,9 +160,9 @@ export function parentNodeNotInCycle(subgraphNetwork: SubgraphNetwork, listNodes * @param subgraphNetwork - The subgraph network object. * @param listNodes - The list of nodes to check for child nodes. * @param sort - Optional. Specifies whether to sort the child nodes (for each node) by their x position. Defaults to false. - * @returns An array of arrays containing the child nodes that are not part of any cycle. + * @returns Promise of an array of arrays containing the child nodes that are not part of any cycle. */ -export function childNodeNotInCycle(subgraphNetwork: SubgraphNetwork, listNodes: string[],sort:boolean=false): string[][] { +export async function childNodeNotInCycle(subgraphNetwork: SubgraphNetwork, listNodes: string[],sort:boolean=false): Promise<string[][]> { const childNodes = listNodes.map((node: string) => { let childNodesI = subgraphNetwork.network.links .filter(link => link.source.id === node) // get link with those node as parent @@ -189,9 +189,9 @@ export function childNodeNotInCycle(subgraphNetwork: SubgraphNetwork, listNodes: * * @param subgraphNetwork - The subgraph network containing the group cycles. * @param groupCycleID - The ID of the group cycle to retrieve the nodes from. - * @returns An array of strings representing the IDs of the nodes placed in the group cycle. + * @returns Promise of an array of strings representing the IDs of the nodes placed in the group cycle. */ -export function getNodesIDPlacedInGroupCycle(subgraphNetwork:SubgraphNetwork,groupCycleID:string):string[]{ +export async function getNodesIDPlacedInGroupCycle(subgraphNetwork:SubgraphNetwork,groupCycleID:string):Promise<string[]>{ if (subgraphNetwork[TypeSubgraph.CYCLEGROUP] && groupCycleID in subgraphNetwork[TypeSubgraph.CYCLEGROUP]){ const groupCycle =subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleID]; if (groupCycle.precalculatedNodesPosition){ @@ -325,19 +325,20 @@ export function cycleMetanodeLink(link:LinkLayout, cycle:boolean=true):{inCycle: * * @param subgraphNetwork - The subgraph network. * @param orderChange - A boolean indicating whether to change the order with group cycle. Default is false. - * @returns The subgraphNetwork and an array of sorted links. + * @returns Promise of the subgraphNetwork and an array of sorted links. */ -export function sortLinksWithAllGroupCycle(subgraphNetwork:SubgraphNetwork,orderChange:boolean=false):{subgraphNetwork:SubgraphNetwork,linksOrdered:LinkLayout[]}{ +export async function sortLinksWithAllGroupCycle(subgraphNetwork:SubgraphNetwork,orderChange:boolean=false):Promise<{subgraphNetwork:SubgraphNetwork,linksOrdered:LinkLayout[]}>{ let links:Link[]=[]; // change ordre with group cycle if (orderChange && subgraphNetwork[TypeSubgraph.CYCLEGROUP]){ // adding edge in right order for each group cycle - Object.keys(subgraphNetwork[TypeSubgraph.CYCLEGROUP]).forEach( (groupCycle) => { - const resultSorting=sortLinksWithGroupCycle(subgraphNetwork,groupCycle); - subgraphNetwork=resultSorting.subgraphNetwork; - links=links.concat(resultSorting.linksOrdered); - }); + for (const groupCycle of Object.keys(subgraphNetwork[TypeSubgraph.CYCLEGROUP])) { + const resultSorting = await sortLinksWithGroupCycle(subgraphNetwork, groupCycle); + subgraphNetwork = resultSorting.subgraphNetwork; + links = links.concat(resultSorting.linksOrdered); + } + // add other links Object.values(subgraphNetwork.network.links).forEach((link) => { if (!links.includes(link)){ @@ -358,15 +359,15 @@ export function sortLinksWithAllGroupCycle(subgraphNetwork:SubgraphNetwork,order * * @param subgraphNetwork - The subgraph network containing the cycles group. * @param groupCycle - The group cycle id to sort the links. - * @returns The subgraphNetwork and an array of sorted links. + * @returns Promise of the subgraphNetwork and an array of sorted links. */ -function sortLinksWithGroupCycle(subgraphNetwork:SubgraphNetwork,groupCycle:string):{subgraphNetwork:SubgraphNetwork,linksOrdered:LinkLayout[]}{ +async function sortLinksWithGroupCycle(subgraphNetwork:SubgraphNetwork,groupCycle:string):Promise<{subgraphNetwork:SubgraphNetwork,linksOrdered:LinkLayout[]}>{ let links:LinkLayout[]=[]; if( subgraphNetwork[TypeSubgraph.CYCLEGROUP] && groupCycle in subgraphNetwork[TypeSubgraph.CYCLEGROUP]){ // sort parent of cycle by x of the child in the cycle // (first : parent of the left node of group cycle) - const parents= neighborsGroupCycle(subgraphNetwork,groupCycle,"parent",true); - const children= neighborsGroupCycle(subgraphNetwork,groupCycle,"child",true); + const parents= await neighborsGroupCycle(subgraphNetwork,groupCycle,"parent",true); + const children= await neighborsGroupCycle(subgraphNetwork,groupCycle,"child",true); let nodeOrder:string[]=[]; let source:"node"|"groupCycle"; @@ -384,9 +385,9 @@ function sortLinksWithGroupCycle(subgraphNetwork:SubgraphNetwork,groupCycle:stri } // get links between the parent (or children) and the group cycle in the right order - nodeOrder.forEach((nodeId) => { + nodeOrder.forEach(async (nodeId) => { // get links for each node - const newLinksOrder = getLinksNodeGroupCycle(subgraphNetwork,nodeId,groupCycle,source); + const newLinksOrder = await getLinksNodeGroupCycle(subgraphNetwork,nodeId,groupCycle,source); // add links newLinksOrder.forEach((newLink) => { links.push(newLink); @@ -410,7 +411,7 @@ function sortLinksWithGroupCycle(subgraphNetwork:SubgraphNetwork,groupCycle:stri * @param source "node" if the parent links are needed, "groupCycle" if the child links are needed * @returns links that are child or parent of a group cycle */ -function getLinksNodeGroupCycle(subgraphNetwork:SubgraphNetwork,nodeId:string,groupCycleId:string,source:"node"|"groupCycle"):LinkLayout[]{ +async function getLinksNodeGroupCycle(subgraphNetwork:SubgraphNetwork,nodeId:string,groupCycleId:string,source:"node"|"groupCycle"):Promise<LinkLayout[]>{ if (source==="node"){ // node to group cycle return Object.values(subgraphNetwork.network.links).filter((link) => { diff --git a/src/composables/ConvertFromNetwork.ts b/src/composables/ConvertFromNetwork.ts index b709a7a..589f53c 100644 --- a/src/composables/ConvertFromNetwork.ts +++ b/src/composables/ConvertFromNetwork.ts @@ -259,10 +259,10 @@ export function networkToAdjacentObject(network:Network):{[key : string]:string[ * @param addNodes - Optional. Specifies whether to include nodes in the DOT string. Default is true. * @param groupOrCluster - Optional. Specifies whether to use "group" or "cluster" for grouping nodes. Default is "cluster". * @param orderChange - Optional. Specifies whether to change the order of links in the DOT string. Default is false. - * @returns The DOT string representation of the SubgraphNetwork. + * @returns Promise of the DOT string representation of the SubgraphNetwork. */ -export function networkToDOT(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, addNodes:boolean=true,groupOrCluster:"group"|"cluster"="cluster",orderChange:boolean=false): string{ - const graphViz=networkToViz(subgraphNetwork,cycle,addNodes,groupOrCluster,orderChange); +export async function networkToDOT(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, addNodes:boolean=true,groupOrCluster:"group"|"cluster"="cluster",orderChange:boolean=false): Promise<string>{ + const graphViz=await networkToViz(subgraphNetwork,cycle,addNodes,groupOrCluster,orderChange); const dotString=graphVizToDot(graphViz); return dotString; } @@ -272,9 +272,9 @@ export function networkToDOT(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, * @param {Network} Network object * @param graphAttributes for viz dot layout (see https://graphviz.org/docs/layouts/dot/) * @param clusters clusters for viz - * @returns {Graph} Return graph object for viz + * @returns {Graph} Return promise of graph object for viz */ -export function networkToViz(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, addNodes:boolean=true,groupOrCluster:"group"|"cluster"="cluster",orderChange:boolean=false): Graph{ +export async function networkToViz(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, addNodes:boolean=true,groupOrCluster:"group"|"cluster"="cluster",orderChange:boolean=false):Promise<Graph>{ if (groupOrCluster==="group" && !addNodes){ console.warn('Group without nodes in the file not taken into account'); @@ -305,7 +305,7 @@ export function networkToViz(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, // insert link (but with cycle metanode if cycle is true) let links:LinkLayout[]=[]; - const resultOrdering=sortLinksWithAllGroupCycle(subgraphNetwork,orderChange); // order of link changed for cycle group + const resultOrdering=await sortLinksWithAllGroupCycle(subgraphNetwork,orderChange); // order of link changed for cycle group links=resultOrdering.linksOrdered; subgraphNetwork=resultOrdering.subgraphNetwork; // BEWARE: do this before adding cycle metanode (because of attribut ordering) links.forEach((link)=>{ diff --git a/src/composables/LayoutDrawCycle.ts b/src/composables/LayoutDrawCycle.ts index 9580dfc..2164a53 100644 --- a/src/composables/LayoutDrawCycle.ts +++ b/src/composables/LayoutDrawCycle.ts @@ -603,7 +603,7 @@ async function findTopCycleNode(subgraphNetwork: SubgraphNetwork, cycleNodes:str // find node with the highest parent (smaller y) // get parent nodes of the cycle - const cycleNodesParent = parentNodeNotInCycle(subgraphNetwork, cycleNodes); + const cycleNodesParent = await parentNodeNotInCycle(subgraphNetwork, cycleNodes); // get the one with highest parent const indexAssociatedMinY= await getIndexNodesAssociatedMinY(subgraphNetwork, cycleNodesParent) const withHighestParent= indexAssociatedMinY.map(i=>cycleNodes[i]); @@ -630,7 +630,7 @@ async function findTopCycleNode(subgraphNetwork: SubgraphNetwork, cycleNodes:str let bottomNode:number; // find node with the lowest child (bigger y) // get child nodes of the cycle - const cycleNodesChild = childNodeNotInCycle(subgraphNetwork, cycleNodes); + const cycleNodesChild = await childNodeNotInCycle(subgraphNetwork, cycleNodes); // get the one with lowest child const indexAssociatedMaxY=await getIndexNodesAssociatedMaxY(subgraphNetwork, cycleNodesChild) @@ -759,8 +759,8 @@ async function nodeMedianX(subgraphNetwork: SubgraphNetwork, listNodes: string[] const size= subgraph.nodes.length; if (fullConstraint){ - const numberParent = await parentNodeNotInCycle(subgraphNetwork, subgraph.nodes).flat().length; - const numberChild = await childNodeNotInCycle(subgraphNetwork, subgraph.nodes).flat().length; + const numberParent = (await parentNodeNotInCycle(subgraphNetwork, subgraph.nodes)).flat().length; + const numberChild = (await childNodeNotInCycle(subgraphNetwork, subgraph.nodes)).flat().length; return {fixedNodes:fixedNodesA,size:size,numberParent:numberParent,numberChild:numberChild}; }else{ return {fixedNodes:fixedNodesA,size:size}; diff --git a/src/composables/LayoutSugiyama.ts b/src/composables/LayoutSugiyama.ts index 3d8725d..6eec571 100644 --- a/src/composables/LayoutSugiyama.ts +++ b/src/composables/LayoutSugiyama.ts @@ -70,7 +70,7 @@ export async function vizLayout(subgraphNetwork:SubgraphNetwork,assignRank:boole // attributes for viz const sep =await getSepAttributesInches(subgraphNetwork.network,subgraphNetwork.networkStyle,factorLenghtEdge); subgraphNetwork.attributs={rankdir: "BT" , newrank:true, compound:true,splines:false,ranksep:sep.rankSep,nodesep:sep.nodeSep,dpi:dpi}; - const dot=networkToDOT(subgraphNetwork,cycle,addNodes,groupOrCluster,orderChange); + const dot=await networkToDOT(subgraphNetwork,cycle,addNodes,groupOrCluster,orderChange); if(printDot) console.log(dot); const json=viz.renderJSON(dot) as JsonViz; subgraphNetwork= await changeNetworkFromViz(json,subgraphNetwork,assignRank); diff --git a/src/composables/__tests__/CalculateRelationCycle.test.ts b/src/composables/__tests__/CalculateRelationCycle.test.ts index 89b2fe2..1a03d61 100644 --- a/src/composables/__tests__/CalculateRelationCycle.test.ts +++ b/src/composables/__tests__/CalculateRelationCycle.test.ts @@ -60,12 +60,12 @@ describe('CalculateRelationCycle', () => { // 0. Get nodes ***************************************************************** describe('getNodesIDPlacedInGroupCycle', () => { - it('should throw error because cycle group not defined in subgraphNetwork when trying to get node id placed inside ', () => { + it('should throw error because cycle group not defined in subgraphNetwork when trying to get node id placed inside ', async () => { // EXPECT - expect(()=>{CalculateRelationCycle.getNodesIDPlacedInGroupCycle(subgraphNetwork,"groupCycle")}).toThrow(); - + await expect(CalculateRelationCycle.getNodesIDPlacedInGroupCycle(subgraphNetwork, "groupCycle")).rejects.toThrow(); }); - it('should get nodes id placed inside group cycle ', () => { + + it('should get nodes id placed inside group cycle ', async() => { // DATA const cycleGroup:Subgraph={ name: "groupCycle", @@ -77,19 +77,18 @@ describe('CalculateRelationCycle', () => { type: TypeSubgraph.CYCLEGROUP }; - subgraphNetwork[TypeSubgraph.CYCLEGROUP]={}; - subgraphNetwork[TypeSubgraph.CYCLEGROUP]["groupCycle"] = cycleGroup; - + subgraphNetwork[TypeSubgraph.CYCLEGROUP]={groupCycle:cycleGroup}; const nodesIDExpected=["node2","node3"]; // TEST - const nodesID=CalculateRelationCycle.getNodesIDPlacedInGroupCycle(subgraphNetwork,"groupCycle"); + const nodesID=await CalculateRelationCycle.getNodesIDPlacedInGroupCycle(subgraphNetwork,"groupCycle"); // EXPECT expect(nodesID).toEqual(nodesIDExpected); }); }); + describe('getNodesPlacedInGroupCycleAsArray', () => { it('should throw error because cycle group not defined in subgraphNetwork when trying to get node placed inside ', () => { @@ -215,7 +214,7 @@ describe('CalculateRelationCycle', () => { describe('childNodeNotInCycle', () => { - it('should return child of a groupcycle, that are not in a cycle , no sorting (and a parent node in cycle)', () => { + it('should return child of a groupcycle, that are not in a cycle , no sorting (and a parent node in cycle)', async() => { // MOCK const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); inCycleMock.mockImplementation( (network,id)=>{ @@ -231,7 +230,7 @@ describe('CalculateRelationCycle', () => { ]; // TEST - const children=CalculateRelationCycle.childNodeNotInCycle(subgraphNetwork,listNodes,false); + const children=await CalculateRelationCycle.childNodeNotInCycle(subgraphNetwork,listNodes,false); // EXPECT expect(children).toEqual(childrenExpected); @@ -239,7 +238,7 @@ describe('CalculateRelationCycle', () => { }); - it('should return child of a groupcycle, that are not in a cycle ,sorting (no parent node in cycle)', () => { + it('should return child of a groupcycle, that are not in a cycle ,sorting (no parent node in cycle)', async() => { // MOCK const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); inCycleMock.mockImplementation( (network,id)=>{ @@ -255,7 +254,7 @@ describe('CalculateRelationCycle', () => { ]; // TEST - const children=CalculateRelationCycle.childNodeNotInCycle(subgraphNetwork,listNodes,true); + const children=await CalculateRelationCycle.childNodeNotInCycle(subgraphNetwork,listNodes,true); // EXPECT expect(children).toEqual(childrenExpected); @@ -266,7 +265,7 @@ describe('CalculateRelationCycle', () => { describe('parentNodeNotInCycle', () => { - it('should return parent of a groupcycle, that are not in a cycle , no sorting (and a parent node in cycle)', () => { + it('should return parent of a groupcycle, that are not in a cycle , no sorting (and a parent node in cycle)', async() => { // MOCK const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); inCycleMock.mockImplementation( (network,id)=>{ @@ -282,14 +281,14 @@ describe('CalculateRelationCycle', () => { ]; // TEST - const parent=CalculateRelationCycle.parentNodeNotInCycle(subgraphNetwork,listNodes,false); + const parent=await CalculateRelationCycle.parentNodeNotInCycle(subgraphNetwork,listNodes,false); // EXPECT expect(parent).toEqual(parentExpected); }); - it('should return parent of a groupcycle, that are not in a cycle , sorting (no parent node in cycle)', () => { + it('should return parent of a groupcycle, that are not in a cycle , sorting (no parent node in cycle)', async() => { // MOCK const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); inCycleMock.mockImplementation( (network,id)=>{ @@ -305,7 +304,7 @@ describe('CalculateRelationCycle', () => { ]; // TEST - const parent=CalculateRelationCycle.parentNodeNotInCycle(subgraphNetwork,listNodes,true); + const parent=await CalculateRelationCycle.parentNodeNotInCycle(subgraphNetwork,listNodes,true); // EXPECT expect(parent).toEqual(parentExpected); @@ -316,13 +315,13 @@ describe('CalculateRelationCycle', () => { describe('neighborsGroupCycle', () => { - it('sould throw an error instead of returning parent/child of a group cycle , because no groupcycle', () => { + it('sould throw an error instead of returning parent/child of a group cycle , because no groupcycle', async() => { // EXPECT - expect(()=>{CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","parent")}).toThrow(); - expect(()=>{CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","child")}).toThrow(); + await expect(CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","parent")).rejects.toThrow(); + await expect(CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","child")).rejects.toThrow(); }); - it('sould return parent/child of a group cycle , but no precalculatedPosition', () => { + it('sould return parent/child of a group cycle , but no precalculatedPosition', async() => { // MOCK const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); @@ -343,8 +342,8 @@ describe('CalculateRelationCycle', () => { const resultExpected:string[]=[]; // TEST - const resultParent=CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","parent"); - const resultChildren=CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","child"); + const resultParent=await CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","parent"); + const resultChildren=await CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","child"); // EXPECT expect(resultParent).toEqual(resultExpected); @@ -352,7 +351,7 @@ describe('CalculateRelationCycle', () => { }); - it('sould return parent/child of a group cycle with neighborsGroupCycle, no sorting', () => { + it('sould return parent/child of a group cycle with neighborsGroupCycle, no sorting', async() => { // MOCK const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); inCycleMock.mockImplementation( (network,id)=>{ @@ -377,8 +376,8 @@ describe('CalculateRelationCycle', () => { const childrenExpected:string[]=["node1","node4","node0"]; // TEST - const resultParent=CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","parent",false); - const resultChildren=CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","child",false); + const resultParent=await CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","parent",false); + const resultChildren=await CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","child",false); // EXPECT expect(resultParent).toEqual(parentExpected); @@ -386,7 +385,7 @@ describe('CalculateRelationCycle', () => { }); - it('sould return parent/child of a group cycle with neighborsGroupCycle, sorting', () => { + it('sould return parent/child of a group cycle with neighborsGroupCycle, sorting', async() => { // MOCK const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); inCycleMock.mockImplementation( (network,id)=>{ @@ -411,8 +410,8 @@ describe('CalculateRelationCycle', () => { const childrenExpected:string[]=["node1","node0","node4"]; // TEST - const resultParent=CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","parent",true); - const resultChildren=CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","child",true); + const resultParent=await CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","parent",true); + const resultChildren=await CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","child",true); // EXPECT expect(resultParent).toEqual(parentExpected); @@ -468,9 +467,9 @@ describe('CalculateRelationCycle', () => { describe('sortLinksWithAllGroupCycle', () => { - it('should not change links order', () => { + it('should not change links order', async() => { // TEST - const result=CalculateRelationCycle.sortLinksWithAllGroupCycle(subgraphNetwork,false); + const result=await CalculateRelationCycle.sortLinksWithAllGroupCycle(subgraphNetwork,false); // EXPECT expect(result.subgraphNetwork).toEqual(subgraphNetwork); @@ -478,7 +477,7 @@ describe('CalculateRelationCycle', () => { }); - it('should change links order, with ordering in (parent)', () => { + it('should change links order, with ordering in (parent)', async() => { // MOCK const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); @@ -494,7 +493,7 @@ describe('CalculateRelationCycle', () => { const subgraph:Subgraph={ name: "groupCycle", - nodes: ["node2","node3"], + nodes: [], precalculatedNodesPosition: { node2: { x: 2, y: 3 }, node3: { x: 3, y: 2 }, @@ -525,7 +524,7 @@ describe('CalculateRelationCycle', () => { ] // TEST - const result=CalculateRelationCycle.sortLinksWithAllGroupCycle(subgraphNetwork,true); + const result=await CalculateRelationCycle.sortLinksWithAllGroupCycle(subgraphNetwork,true); //console.log(result.linksOrdered); // EXPECT @@ -534,7 +533,7 @@ describe('CalculateRelationCycle', () => { }); - it('should change links order, with ordering out (child)', () => { + it('should change links order, with ordering out (child)', async() => { // MOCK : declare node in cycle const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); @@ -579,7 +578,7 @@ describe('CalculateRelationCycle', () => { ] // TEST - const result=CalculateRelationCycle.sortLinksWithAllGroupCycle(subgraphNetwork,true); + const result=await CalculateRelationCycle.sortLinksWithAllGroupCycle(subgraphNetwork,true); //console.log(result.linksOrdered); // EXPECT @@ -587,7 +586,7 @@ describe('CalculateRelationCycle', () => { expect(result.linksOrdered).toEqual(expectedLinksOrdered); }); - }); + }); describe('getLinksForListNodes', () => { test('getLinksForListNodes', () => { @@ -610,7 +609,7 @@ describe('CalculateRelationCycle', () => { }); -// 2. Get graph ***************************************************************** +// // 2. Get graph ***************************************************************** describe('get Graph For Cycle Group', () => { test('getListNodeLinksForCycleGroupAsArray', () => { @@ -695,7 +694,7 @@ describe('CalculateRelationCycle', () => { }); }); -// 3. Get relation cycle ***************************************************************** +// // 3. Get relation cycle ***************************************************************** // parentCycle describe('parentCycle', () => { diff --git a/src/composables/__tests__/ConvertFromNetwork.test.ts b/src/composables/__tests__/ConvertFromNetwork.test.ts index c2f5f3d..bbf5d93 100644 --- a/src/composables/__tests__/ConvertFromNetwork.test.ts +++ b/src/composables/__tests__/ConvertFromNetwork.test.ts @@ -234,25 +234,25 @@ describe('ConvertFromNetwork', () => { - it('should throw error when converting network to DOT string, but no size of cycleGroup', () => { + it('should throw error when converting network to DOT string, but no size of cycleGroup', async () => { // DATA delete cycleGroup.width; delete cycleGroup.height; // EXPECT - expect(()=>{ConvertFromNetwork.networkToViz(subgraphNetwork,true)}).toThrow(); + await expect(ConvertFromNetwork.networkToViz(subgraphNetwork,true)).rejects.toThrow(); }); - it('should throw error when converting network to DOT string, change order but no ordering', () => { + it('should throw error when converting network to DOT string, change order but no ordering', async() => { // DATA delete cycleGroup.ordering; // EXPECT - expect(()=>{ConvertFromNetwork.networkToViz(subgraphNetwork,true,true,"cluster",true)}).toThrow(); + await expect(ConvertFromNetwork.networkToViz(subgraphNetwork,true,true,"cluster",true)).rejects.toThrow(); }); - it('should convert network to DOT string, default args', () => { + it('should convert network to DOT string, default args', async () => { // DATA const resultExpected:Graph={ "graphAttributes":{}, @@ -271,14 +271,14 @@ describe('ConvertFromNetwork', () => { "subgraphs":[{"name":"cluster_mainChain","nodes":[{"name":"node2"},{"name":"node3"},{"name":"cycleGroup"}]}]} // TEST - const result = ConvertFromNetwork.networkToViz(subgraphNetwork,true,true,"cluster",false); + const result = await ConvertFromNetwork.networkToViz(subgraphNetwork,true,true,"cluster",false); // EXPECT expect(result).toEqual(resultExpected); }); - it('should convert network to DOT string, no cyle', () => { + it('should convert network to DOT string, no cyle', async () => { // DATA const resultExpected:Graph={ "graphAttributes": {}, @@ -307,14 +307,14 @@ describe('ConvertFromNetwork', () => { // TEST - const result = ConvertFromNetwork.networkToViz(subgraphNetwork,false); + const result = await ConvertFromNetwork.networkToViz(subgraphNetwork,false); // EXPECT expect(result).toEqual(resultExpected); }); - it('should convert network to DOT string, add no nodes', () => { + it('should convert network to DOT string, add no nodes', async() => { // DATA const resultExpected:Graph={ "graphAttributes": {}, @@ -338,13 +338,13 @@ describe('ConvertFromNetwork', () => { // TEST - const result = ConvertFromNetwork.networkToViz(subgraphNetwork,true,false); + const result = await ConvertFromNetwork.networkToViz(subgraphNetwork,true,false); // EXPECT expect(result).toEqual(resultExpected); }); - it('should convert network to DOT string, add no nodes et no cycle', () => { + it('should convert network to DOT string, add no nodes et no cycle', async () => { // DATA const resultExpected:Graph={ "graphAttributes": {}, @@ -367,13 +367,13 @@ describe('ConvertFromNetwork', () => { // TEST - const result = ConvertFromNetwork.networkToViz(subgraphNetwork,false,false); + const result = await ConvertFromNetwork.networkToViz(subgraphNetwork,false,false); // EXPECT expect(result).toEqual(resultExpected); }); - it('should convert network to DOT string, group for main chain', () => { + it('should convert network to DOT string, group for main chain', async () => { // DATA const resultExpected:Graph={ "graphAttributes": {}, @@ -394,14 +394,14 @@ describe('ConvertFromNetwork', () => { // TEST - const result = ConvertFromNetwork.networkToViz(subgraphNetwork,true,true, "group"); + const result = await ConvertFromNetwork.networkToViz(subgraphNetwork,true,true, "group"); // EXPECT expect(result).toEqual(resultExpected); }); - it('should convert network to DOT string, several cycleGroup', () => { + it('should convert network to DOT string, several cycleGroup', async() => { // DATA const cycleGroup2 :Subgraph={ name: "cycleGroup2", @@ -444,14 +444,14 @@ describe('ConvertFromNetwork', () => { } // TEST - const result = ConvertFromNetwork.networkToViz(subgraphNetwork); + const result = await ConvertFromNetwork.networkToViz(subgraphNetwork); // EXPECT expect(result).toEqual(resultExpected); }); - it('should convert network to DOT string, order change', () => { + it('should convert network to DOT string, order change', async() => { // MOCK sortLinksWithAllGroupCycleMock.mockReturnValue({ subgraphNetwork:subgraphNetwork, @@ -490,7 +490,7 @@ describe('ConvertFromNetwork', () => { // TEST - const result = ConvertFromNetwork.networkToViz(subgraphNetwork,true,true, "cluster",true); + const result = await ConvertFromNetwork.networkToViz(subgraphNetwork,true,true, "cluster",true); // EXPECT expect(result).toEqual(resultExpected); @@ -528,12 +528,12 @@ describe('ConvertFromNetwork', () => { }); - test('networkToDOT', () => { + test('networkToDOT', async () => { // DATA const resultExpected = "strict digraph G {\n graph []\nsubgraph cluster_mainChain {\nnode2;node3;cycleGroup;}\nnode1 [height=\"0.35\", width=\"0.35\", fixedsize=\"true\"];\nnode2 [height=\"0.35\", width=\"0.35\", fixedsize=\"true\"];\nnode3 [height=\"0.35\", width=\"0.35\", fixedsize=\"true\"];\ncycleGroup [fixedsize=\"true\", height=\"0.01\", width=\"0.01\"];\nnode1 -> node2;\nnode2 -> node3;\nnode3 -> cycleGroup;\n}" // TEST - const result = ConvertFromNetwork.networkToDOT(subgraphNetwork); + const result = await ConvertFromNetwork.networkToDOT(subgraphNetwork); // EXPECT expect(result).toEqual(resultExpected); diff --git a/src/composables/__tests__/LayoutSugiyama.test.ts b/src/composables/__tests__/LayoutSugiyama.test.ts index 0b00a22..d21f1e5 100644 --- a/src/composables/__tests__/LayoutSugiyama.test.ts +++ b/src/composables/__tests__/LayoutSugiyama.test.ts @@ -133,7 +133,7 @@ describe('LayoutSugiyama', () => { getSepAttributesInchesMock.mockReturnValue(Promise.resolve({rankSep: 4, nodeSep: 4})); const networkToDOTMock = jest.spyOn(ConvertFromNetwork, 'networkToDOT'); - networkToDOTMock.mockReturnValue(DOT); + networkToDOTMock.mockReturnValue(Promise.resolve(DOT)); const changeNetworkFromVizMock = jest.spyOn(ConvertToNetwork, 'changeNetworkFromViz'); changeNetworkFromVizMock.mockImplementation(async(json,subgraphNetwork,assignRank)=>{ -- GitLab From 80ad5c545af50d9312d3d43bb1f443c84be0f9ae Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Mon, 23 Sep 2024 18:23:09 +0200 Subject: [PATCH 3/3] test draw cycle --- src/composables/CalculateRelationCycle.ts | 1 + src/composables/CalculateSize.ts | 2 +- src/composables/ConvertToNetwork.ts | 8 +- src/composables/LayoutDrawCycle.ts | 84 +- src/composables/LayoutManageSideCompounds.ts | 2 +- .../__tests__/LayoutDrawCycle.test.ts | 1201 +++++++++++++++++ 6 files changed, 1251 insertions(+), 47 deletions(-) create mode 100644 src/composables/__tests__/LayoutDrawCycle.test.ts diff --git a/src/composables/CalculateRelationCycle.ts b/src/composables/CalculateRelationCycle.ts index 6f97b6c..cdbc95a 100644 --- a/src/composables/CalculateRelationCycle.ts +++ b/src/composables/CalculateRelationCycle.ts @@ -252,6 +252,7 @@ export function getNodesPlacedInGroupCycleAsArray(subgraphNetwork:SubgraphNetwor * If the group cycle ID is not found or the precalculated node positions are not available, null is returned. */ export function getNodesPlacedInGroupCycleAsObject(subgraphNetwork:SubgraphNetwork,groupCycleID:string):{[key:string]:Coordinate}{ + console.log(JSON.stringify(subgraphNetwork)); if (subgraphNetwork[TypeSubgraph.CYCLEGROUP] && groupCycleID in subgraphNetwork[TypeSubgraph.CYCLEGROUP]){ const groupCycle =subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleID]; if (groupCycle.precalculatedNodesPosition){ diff --git a/src/composables/CalculateSize.ts b/src/composables/CalculateSize.ts index e4056b5..6fde528 100644 --- a/src/composables/CalculateSize.ts +++ b/src/composables/CalculateSize.ts @@ -4,9 +4,9 @@ import { Node } from "@metabohub/viz-core/src/types/Node"; import { SubgraphNetwork } from "../types/SubgraphNetwork"; import { Subgraph, TypeSubgraph } from "../types/Subgraph"; import { Coordinate, Size } from "../types/CoordinatesSize"; +import { GraphStyleProperties } from "@metabohub/viz-core/src/types/GraphStyleProperties"; // Composable imports -import { GraphStyleProperties } from "@metabohub/viz-core/src/types/GraphStyleProperties"; import { inCycle, isSideCompound } from "./GetSetAttributsNodes"; // General imports diff --git a/src/composables/ConvertToNetwork.ts b/src/composables/ConvertToNetwork.ts index 4b0f600..db9a26c 100644 --- a/src/composables/ConvertToNetwork.ts +++ b/src/composables/ConvertToNetwork.ts @@ -5,16 +5,14 @@ import { Network } from '@metabohub/viz-core/src/types/Network'; import type { Node } from "@metabohub/viz-core/src/types/Node"; import { NetworkLayout } from '../types/NetworkLayout'; import { SubgraphNetwork } from '../types/SubgraphNetwork'; +import { TypeSubgraph } from '../types/Subgraph'; + // Composable imports import { assignRankOrder } from './CalculateStartNodes'; import { getSizeNodePixel } from './CalculateSize'; -// General imports -// import cytoscape, { ElementDefinition } from 'cytoscape'; -// import dagre from 'dagrejs/dist/dagre.js'; -import { type } from 'os'; -import { TypeSubgraph } from '../types/Subgraph'; + /** * This file contains functions to get or update a network from another format. diff --git a/src/composables/LayoutDrawCycle.ts b/src/composables/LayoutDrawCycle.ts index 2164a53..c6e5fb6 100644 --- a/src/composables/LayoutDrawCycle.ts +++ b/src/composables/LayoutDrawCycle.ts @@ -18,11 +18,8 @@ import { updateNodeMetadataSubgraph } from "./SubgraphForSubgraphNetwork"; // General imports -import { group } from "console"; -import { start } from "repl"; import * as d3 from 'd3'; -import { link } from "fs"; -import { emit } from "process"; + /** * This file contains functions to calculate the coordinates of nodes to draw them as circle. @@ -155,7 +152,6 @@ export async function coordinateAllCycles(subgraphNetwork:SubgraphNetwork,allowI const scores = await calculateAllScoresForSubgraphs(subgraphNetwork, parentCycles, true); parentCycles.sort( (a, b) => sortingCycleForDrawing(scores[a.name],scores[b.name],true)); const largestParentCycle = parentCycles[0]; // get largest cycle - cycleGroups[groupName].nodes.push(largestParentCycle.name); // add it to the current group of cycle await coordinateCycle(subgraphNetwork, largestParentCycle.name,groupName); // give coordinate for largest cycle @@ -262,7 +258,6 @@ async function coordinateCycle(subgraphNetwork:SubgraphNetwork, cycleToDrawID:st } else { // several node in common with other cycle(s) ---------------------------------------------------------------------------------- - subgraphNetwork= await lineCycleCoordinates(subgraphNetwork,cycleToDrawID,groupCycleName); } @@ -315,16 +310,19 @@ async function independentCycleCoordinates(subgraphNetwork:SubgraphNetwork,cycle */ async function tangentCycleCoordinates(subgraphNetwork:SubgraphNetwork,cycleToDrawID:string,groupCycleName:string,nodeFixed:NodeLayout,nodesPlaced:string[],allowInterncircle:boolean=false):Promise<SubgraphNetwork>{ const network = subgraphNetwork.network; + + // if cycle to draw not in object or fixed nodes lack information : throw error if (!subgraphNetwork[TypeSubgraph.CYCLE] || !subgraphNetwork[TypeSubgraph.CYCLE][cycleToDrawID]) throw new Error("cycle not in subgraphNetwork"); const cycleNodes=subgraphNetwork.cycles[cycleToDrawID].nodes; + if (!nodeFixed.metadataLayout || !nodeFixed.metadataLayout.fixedInCircle) throw new Error("fixed node for tangent drawing not in a circle"); + // get fixed node coordinates (in the group cycle) - if (!nodeFixed.metadataLayout || !nodeFixed.metadataLayout[TypeSubgraph.CYCLEGROUP]) throw new Error("node not a group cycle"); - const groupCycleFixed=nodeFixed.metadataLayout[TypeSubgraph.CYCLEGROUP]; - if (!subgraphNetwork[TypeSubgraph.CYCLEGROUP] || !subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleFixed] || - !subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleFixed].precalculatedNodesPosition || - !subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleFixed].precalculatedNodesPosition[nodeFixed.id]) throw new Error("fixed node not in group cycle, or no precalculated position"); - const getCoordNodeFixed=subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleFixed].precalculatedNodesPosition[nodeFixed.id]; + if (!subgraphNetwork[TypeSubgraph.CYCLEGROUP] || !subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleName] || + !subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleName].precalculatedNodesPosition || + !subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleName].precalculatedNodesPosition[nodeFixed.id]) throw new Error("fixed node not in group cycle, or no precalculated position"); + const getCoordNodeFixed=subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleName].precalculatedNodesPosition[nodeFixed.id]; + if (getCoordNodeFixed.x==null || getCoordNodeFixed.y==null) throw new Error("fixed node position not defined"); const coordNodeFixed={x:getCoordNodeFixed.x,y:getCoordNodeFixed.y}; @@ -338,8 +336,7 @@ async function tangentCycleCoordinates(subgraphNetwork:SubgraphNetwork,cycleToDr subgraphNetwork[TypeSubgraph.CYCLE][cycleToDrawID].radiusCycle=radius; //centroid depending on fixed cycle - if (!nodeFixed.metadataLayout.fixedInCircle) throw new Error("fixed node for tangent drawing not in a circle"); - const circleAlreadyDrawn=nodeFixed.metadataLayout.fixedInCircle as string; + const circleAlreadyDrawn=nodeFixed.metadataLayout.fixedInCircle; if (!subgraphNetwork[TypeSubgraph.CYCLE] || !subgraphNetwork[TypeSubgraph.CYCLE][circleAlreadyDrawn]) throw new Error("fixed node for tangent drawing not in a circle"); const radiusFixedCycle=subgraphNetwork[TypeSubgraph.CYCLE][circleAlreadyDrawn].radiusCycle; @@ -362,6 +359,8 @@ async function tangentCycleCoordinates(subgraphNetwork:SubgraphNetwork,cycleToDr } centroidX = centroidFixedCycle.x + d * Math.cos(fixedAngle); centroidY = centroidFixedCycle.y + d * Math.sin(fixedAngle); + centroidX=Number(centroidX.toFixed(2)); + centroidY=Number(centroidY.toFixed(2)); subgraphNetwork[TypeSubgraph.CYCLE][cycleToDrawID].centroidCycle={x:centroidX,y:centroidY}; @@ -452,9 +451,11 @@ async function cycleNodesCoordinates(cycleName:string,cycle:string[],centroidX:n const nodeNetwork=network.nodes[node]; // coordinates of the node // positive shift angle rotate cycle to the right, negative to the left - const x = centroidX + radius * Math.cos(2 * Math.PI * i / cycle.length + shiftAngle ); - const y = centroidY + radius * Math.sin(2 * Math.PI * i / cycle.length + shiftAngle ); - + let x = centroidX + radius * Math.cos(2 * Math.PI * i / cycle.length + shiftAngle ); + let y = centroidY + radius * Math.sin(2 * Math.PI * i / cycle.length + shiftAngle ); + x=Number(x.toFixed(2)); + y=Number(y.toFixed(2)); + // Give position if not fixed in circle if(!nodeNetwork.metadataLayout || !nodeNetwork.metadataLayout.fixedInCircle){ @@ -464,9 +465,9 @@ async function cycleNodesCoordinates(cycleName:string,cycle:string[],centroidX:n positionBefore[node]=assignedCoordinates.positionBefore; // Fix the nodes - if (!nodeNetwork.metadata) nodeNetwork.metadata={}; - nodeNetwork.metadata.fixedInCycle= true; - nodeNetwork.metadata.fixedCycle= cycleName; + if (!nodeNetwork.metadataLayout) nodeNetwork.metadataLayout={}; + nodeNetwork.metadataLayout.isFixed= true; + nodeNetwork.metadataLayout.fixedInCircle= cycleName; } }); @@ -508,8 +509,10 @@ async function lineNodesCoordinates(start: {x: number, y: number}, end: {x: numb // coordinates of the node let d = nodeDistance * (i + 1); - const x= start.x + ux * d; - const y = start.y + uy * d; + let x= start.x + ux * d; + let y = start.y + uy * d; + x=Number(x.toFixed(2)); + y=Number(y.toFixed(2)); // assign coordinates to the node, and get the position before const assignedCoordinates= assignCoordinateToNode(subgraphNetwork,node,x,y,groupCycleName); @@ -541,8 +544,8 @@ async function forceGroupCycle(subgraphNetwork:SubgraphNetwork, groupCycleName:s // get subgraph for groupCycle const graph =getListNodeLinksForCycleGroupAsArray(subgraphNetwork,groupCycleName,true); - // need to apply force ? + if (!graph.nodes || graph.nodes.length===0) return subgraphNetwork; const nullNode= graph.nodes.filter(node=>node.fx==undefined || node.fy==undefined); // node not fixed (?) if (nullNode.length==0){ return subgraphNetwork; @@ -573,8 +576,10 @@ async function forceGroupCycle(subgraphNetwork:SubgraphNetwork, groupCycleName:s // get the new position of the nodes graph.nodes.forEach(node => { if(node.x!==undefined && isFinite(node.x) && node.y!==undefined && isFinite(node.y)){ + // update x,y if not fixed precalculatedNodesPosition[node.id] = { x: node.x, y: node.y }; - }else{ + }else if (node.fx==undefined && node.fy==undefined) { + // if no x,y and not fixed : problem throw new Error("error in node coordinates after force layout"); } }); @@ -631,11 +636,9 @@ async function findTopCycleNode(subgraphNetwork: SubgraphNetwork, cycleNodes:str // find node with the lowest child (bigger y) // get child nodes of the cycle const cycleNodesChild = await childNodeNotInCycle(subgraphNetwork, cycleNodes); - // get the one with lowest child const indexAssociatedMaxY=await getIndexNodesAssociatedMaxY(subgraphNetwork, cycleNodesChild) const withLowestChild = indexAssociatedMaxY.map(i=>cycleNodes[i]); - if (withLowestChild.length>=1){ if(withLowestChild.length==1){ bottomNode=cycleNodes.indexOf(withLowestChild[0]); @@ -663,7 +666,7 @@ async function findTopCycleNode(subgraphNetwork: SubgraphNetwork, cycleNodes:str * * @param subgraphNetwork - The subgraph network object. * @param associatedListNodes - The list of list of nodes. - * @returns An array of indices representing lists containing minimum y-coordinate nodes. + * @returns An array of indices representing nodes containing minimum y-coordinate nodes. */ async function getIndexNodesAssociatedMinY(subgraphNetwork: SubgraphNetwork, associatedListNodes: string[][]): Promise<number[]> { const network=subgraphNetwork.network; @@ -691,7 +694,7 @@ async function getIndexNodesAssociatedMinY(subgraphNetwork: SubgraphNetwork, ass * * @param subgraphNetwork - The subgraph network object. * @param associatedListNodes - The list of list of nodes. - * @returns An array of indices representing lists containing maximum y-coordinate nodes. + * @returns An array of indices representing nodes containing maximum y-coordinate nodes. */ async function getIndexNodesAssociatedMaxY(subgraphNetwork: SubgraphNetwork, associatedListNodes: string[][]): Promise<number[]> { const network=subgraphNetwork.network; @@ -752,9 +755,11 @@ async function nodeMedianX(subgraphNetwork: SubgraphNetwork, listNodes: string[] */ async function scoreSortingCycleForDrawing(subgraphNetwork:SubgraphNetwork,subgraph:Subgraph,fullConstraint:boolean=false):Promise<{fixedNodes:number,size:number,numberParent?:number,numberChild?:number}>{ const network=subgraphNetwork.network; + + if (subgraph.nodes.length===0) throw new Error("no node in cycle"); // number of fixed nodes - const fixedNodesA = subgraph.nodes.filter(node => network.nodes[node].metadataLayout && network.nodes[node].metadataLayout.fixedInCircle).length; + const fixedNodesA = subgraph.nodes.filter(node => network.nodes[node] && network.nodes[node].metadataLayout && network.nodes[node].metadataLayout.fixedInCircle).length; // size const size= subgraph.nodes.length; @@ -878,10 +883,9 @@ function addNewCycleGroup(subgraphNetwork:SubgraphNetwork, groupName:string):Sub async function updateGroupCycles(remainingCycles: Subgraph[], subgraphNetwork: SubgraphNetwork, group: number, groupCycleName: string): Promise<{subgraphNetwork: SubgraphNetwork, group: number}> { if (! subgraphNetwork[TypeSubgraph.CYCLEGROUP] || ! subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleName]) throw new Error("Group cycle not in subgraphNetwork"); const cycleGroup=subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleName]; - // check if group cycle is drawn, that is, no fixed nodes for other cycles (other cycle independant) const groupCycleIsDraw = isRemainingCycleIndepOfDrawing(remainingCycles, subgraphNetwork); - + if (groupCycleIsDraw && cycleGroup.precalculatedNodesPosition) { // force algo for node that have null position @@ -917,11 +921,11 @@ function isRemainingCycleIndepOfDrawing(remainingCycles:Subgraph[], subgraphNetw const network = subgraphNetwork.network; - if (remainingCycles.every(cycle => - cycle.nodes.every(node => - !network.nodes[node].metadataLayout || !network.nodes[node].metadataLayout.isFixed - ) - )) { + const isAllNodeUnfixed=remainingCycles.every( + cycle => ( + cycle.nodes.every(node => !network.nodes[node].metadataLayout || !network.nodes[node].metadataLayout.isFixed) + )); + if (isAllNodeUnfixed) { // All nodes in all remaining cycles are unfixed return true; } else { @@ -940,7 +944,8 @@ function isRemainingCycleIndepOfDrawing(remainingCycles:Subgraph[], subgraphNetw async function getRadiusSize(cycle:string[],network:Network,styleNetwork:GraphStyleProperties):Promise<number>{ const nodes=Object.values(network.nodes).filter(node=>cycle.includes(node.id)); const meanSize=await getMeanNodesSizePixel(nodes,styleNetwork); - return cycle.length*(meanSize.height+meanSize.width)/2; + const radius= cycle.length*(meanSize.height+meanSize.width)/2; + return Number(radius.toFixed(2)); } @@ -1020,9 +1025,8 @@ function undoIfCreateOverlap(subgraphNetwork:SubgraphNetwork,groupCycleName:stri if (!groupCycle.precalculatedNodesPosition) groupCycle.precalculatedNodesPosition={}; const xBefore=positionBefore[nodeID].x; const yBefore=positionBefore[nodeID].y; - if (!groupCycle.precalculatedNodesPosition[nodeID]){ - groupCycle.precalculatedNodesPosition[nodeID]={x:xBefore,y:yBefore}; + throw new Error("node supposed to have precalculated position in group cycle, because cause overlap"); } else { const node=groupCycle.precalculatedNodesPosition[nodeID]; node.x = xBefore; @@ -1084,7 +1088,7 @@ async function getUnfixedIntervals(nodes:string[],subgraphNetwork:SubgraphNetwor const network=subgraphNetwork.network; nodes.forEach((nodeID,i) => { const node=network.nodes[nodeID]; - if (node.metadataLayout && !node.metadataLayout.fixedInCircle) { + if (!node.metadataLayout || !node.metadataLayout.fixedInCircle) { if (start === null) { start = i; } diff --git a/src/composables/LayoutManageSideCompounds.ts b/src/composables/LayoutManageSideCompounds.ts index 4593c9e..8505e6b 100644 --- a/src/composables/LayoutManageSideCompounds.ts +++ b/src/composables/LayoutManageSideCompounds.ts @@ -9,7 +9,7 @@ import { GraphStyleProperties } from "@metabohub/viz-core/src/types/GraphStylePr // Composable imports import { removeAllSelectedNodes , duplicateAllNodesByAttribut} from "./VizCoreFunctions"; -import { getMeanNodesSizePixel, inchesToPixels, minEdgeLength as minEdgeLength, pixelsToInches } from "./CalculateSize"; +import { getMeanNodesSizePixel, inchesToPixels, minEdgeLength, pixelsToInches } from "./CalculateSize"; import { sideCompoundAttribute,isDuplicate, isReaction, isSideCompound, setAsSideCompound } from "./GetSetAttributsNodes"; // General imports diff --git a/src/composables/__tests__/LayoutDrawCycle.test.ts b/src/composables/__tests__/LayoutDrawCycle.test.ts new file mode 100644 index 0000000..0610d59 --- /dev/null +++ b/src/composables/__tests__/LayoutDrawCycle.test.ts @@ -0,0 +1,1201 @@ +// Type imports +import { Network } from '@metabohub/viz-core/src/types/Network'; +import { SubgraphNetwork } from '../../types/SubgraphNetwork'; +import { NetworkLayout, NodeLayout } from '../../types/NetworkLayout'; +import { Subgraph, TypeSubgraph } from '../../types/Subgraph'; +import { Link } from '@metabohub/viz-core/src/types/Link'; +import { Coordinate, CoordinateNull } from '../../types/CoordinatesSize'; + + +// Composable imports +import * as LayoutDrawCycle from '../LayoutDrawCycle'; +import * as CalculateRelationCycle from '../CalculateRelationCycle'; +import * as SubgraphForSubgraphNetwork from '../SubgraphForSubgraphNetwork'; +import * as CalculateOverlaps from '../CalculateOverlaps'; +import * as CalculateSize from '../CalculateSize'; +import { get } from 'http'; + + + +jest.mock('d3', () => { + return { + forceSimulation:jest.fn((nodes) => { + nodes[3].x=34; + nodes[3].y=34; + return { + force: jest.fn().mockReturnThis(), + alphaMin: jest.fn().mockReturnThis(), + stop: jest.fn().mockReturnThis(), + tick: jest.fn().mockReturnThis(), + alpha: jest.fn(() => {return 0.3}), + };}), + forceLink: jest.fn(() => ({ + id: jest.fn().mockReturnThis(), + links: jest.fn().mockReturnThis(), + distance: jest.fn().mockReturnThis(), + strength: jest.fn().mockReturnThis(), + })), + forceManyBody: jest.fn(() => ({ + strength: jest.fn().mockReturnThis(), + })), + }; +}); + +function getNodeWithMinYInFirstCycleGroup(subgraphNetwork: SubgraphNetwork): string | null { + + let minYNodeId: string | null = null; + let minY: number = Infinity; + + let cycleGroup: Subgraph; + if (!subgraphNetwork[TypeSubgraph.CYCLEGROUP]) return null; + else { + cycleGroup=subgraphNetwork[TypeSubgraph.CYCLEGROUP]["cycle_group_0"]; + const position= cycleGroup.precalculatedNodesPosition; + for (const nodeId in position) { + const pos = position[nodeId]; + if (pos.y && pos.y < minY) { + minY = pos.y; + minYNodeId = nodeId; + } + } + } + + return minYNodeId; +} + +describe('LayoutDrawCycle',()=>{ + + let parentNodeNotInCycleMock:jest.SpyInstance; + let childNodeNotInCycleMock:jest.SpyInstance; + let getListNodeLinksForCycleGroupAsArrayMock:jest.SpyInstance; + let getListNodeLinksForCycleGroupAsObjectMock:jest.SpyInstance; + let getNodesPlacedInGroupCycleAsObjectMock:jest.SpyInstance; + let isIntersectionGraphMock:jest.SpyInstance; + let isOverlapNodesMock:jest.SpyInstance; + let isOverlapNodesEdgesMock:jest.SpyInstance; + let updateNodeMetadataSubgraphMock:jest.SpyInstance; + let getMeanNodesSizePixelMock:jest.SpyInstance; + let medianEdgeLengthMock:jest.SpyInstance; + let rectangleSizeMock:jest.SpyInstance; + + + + beforeEach(() => { + + getMeanNodesSizePixelMock = jest.spyOn(CalculateSize, 'getMeanNodesSizePixel'); + getMeanNodesSizePixelMock.mockReturnValue(Promise.resolve({width:2,height:2})); + + parentNodeNotInCycleMock = jest.spyOn(CalculateRelationCycle, 'parentNodeNotInCycle'); + parentNodeNotInCycleMock.mockReturnValue([]); + + childNodeNotInCycleMock = jest.spyOn(CalculateRelationCycle, 'childNodeNotInCycle'); + childNodeNotInCycleMock.mockReturnValue([]); + + updateNodeMetadataSubgraphMock = jest.spyOn(SubgraphForSubgraphNetwork, 'updateNodeMetadataSubgraph'); + updateNodeMetadataSubgraphMock.mockImplementation(); + + isIntersectionGraphMock = jest.spyOn(CalculateOverlaps, 'isIntersectionGraph'); + isIntersectionGraphMock.mockReturnValue(false); + + isOverlapNodesMock = jest.spyOn(CalculateOverlaps, 'isOverlapNodes'); + isOverlapNodesMock.mockReturnValue(false); + + isOverlapNodesEdgesMock = jest.spyOn(CalculateOverlaps, 'isOverlapNodesEdges'); + isOverlapNodesEdgesMock.mockReturnValue(false); + + medianEdgeLengthMock= jest.spyOn(CalculateSize, 'medianEdgeLength'); + medianEdgeLengthMock.mockReturnValue(5); + + getListNodeLinksForCycleGroupAsArrayMock = jest.spyOn(CalculateRelationCycle, 'getListNodeLinksForCycleGroupAsArray'); + getListNodeLinksForCycleGroupAsArrayMock.mockReturnValue({}); + + getListNodeLinksForCycleGroupAsObjectMock = jest.spyOn(CalculateRelationCycle, 'getListNodeLinksForCycleGroupAsObject'); + getListNodeLinksForCycleGroupAsObjectMock.mockReturnValue({}); + + getNodesPlacedInGroupCycleAsObjectMock = jest.spyOn(CalculateRelationCycle, 'getNodesPlacedInGroupCycleAsObject'); + getNodesPlacedInGroupCycleAsObjectMock.mockReturnValue({}); + + rectangleSizeMock = jest.spyOn(CalculateSize, 'rectangleSize'); + rectangleSizeMock.mockReturnValue({width:0,height:0,center:{x:0,y:0}}); + + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('coordinateAllCycles',()=>{ + + it('should throw error because all cycle have parent subgraph of type cycle',async ()=>{ + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0"], + parentSubgraph:{name:"cycleParent",type:TypeSubgraph.CYCLE} + }; + + const network:NetworkLayout={ + id:"network", + nodes:{node0:{id:"node0",x:0,y:0}}, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph} + }; + + // TEST & EXPECT + await expect(LayoutDrawCycle.coordinateAllCycles(subgraphNetwork)).rejects.toThrow(); + + }); + + it('should throw error because cycle have no node',async ()=>{ + + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:[], + }; + + const network:NetworkLayout={ + id:"network", + nodes:{}, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph} + }; + + // TEST & EXPECT + await expect(LayoutDrawCycle.coordinateAllCycles(subgraphNetwork)).rejects.toThrow(); + + }); + + it('should throw error because cycle have non existant node',async()=>{ + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0"], + }; + + const network:NetworkLayout={ + id:"network", + nodes:{}, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph} + }; + + // TEST & EXPECT + await expect(LayoutDrawCycle.coordinateAllCycles(subgraphNetwork)).rejects.toThrow(); + + }); + + it('should calculate coordinates for a cycle without parents and children, and get size of metanode', async()=>{ + + // MOCK + getNodesPlacedInGroupCycleAsObjectMock = jest.spyOn(CalculateRelationCycle, 'getNodesPlacedInGroupCycleAsObject'); + getNodesPlacedInGroupCycleAsObjectMock.mockImplementation( () => { + if (!subgraphNetwork[TypeSubgraph.CYCLEGROUP]) return; + const position=subgraphNetwork[TypeSubgraph.CYCLEGROUP]["cycle_group_0"].precalculatedNodesPosition; + if (position && position.node0.x === 0 && position.node0.y === -6 && + position.node1.x === 5.2 && position.node1.y === 3 && + position.node2.x === -5.2 && position.node2.y === 3) { + return {node0:{x:0,y:-6},node1:{x:5.2,y:3},node2:{x:-5.2,y:3}}; + } + } + ); + + rectangleSizeMock = jest.spyOn(CalculateSize, 'rectangleSize'); + rectangleSizeMock.mockReturnValue({width:10.4,height:9,center:{x:0,y:-1.5}}); + + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node2"], + }; + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0} + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph} + }; + + const subgraphNetworkExpected:SubgraphNetwork={"network":{"id":"network", + "nodes":{"node0":{"id":"node0","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node1":{"id":"node1","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node2":{"id":"node2","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}}, + "links":[]}, + "networkStyle":{}, + "cycles":{"cycle":{"name":"cycle","type":TypeSubgraph.CYCLE,"nodes":["node0","node1","node2"],"radiusCycle":6,"centroidCycle":{"x":0,"y":0}}}, + "cyclesGroup":{"cycle_group_0":{"name":"cycle_group_0","nodes":["cycle"],"type":TypeSubgraph.CYCLEGROUP,"precalculatedNodesPosition":{"node0":{"x":0,"y":-6},"node1":{"x":5.2,"y":3},"node2":{"x":-5.2,"y":3}},"width":10.4,"height":9,"originalPosition":{"x":0,"y":-1.5}}}} + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + + // EXPECT + expect(updateNodeMetadataSubgraphMock).toHaveBeenCalledTimes(3); + expect(rectangleSizeMock).toHaveBeenCalledTimes(1); + expect(result).toEqual(subgraphNetworkExpected); + + }); + + it('should find top node for a cycle with parents at different levels', async()=>{ + + // MOCK + parentNodeNotInCycleMock.mockReturnValue([ + [],["node3"],["node4"] + ]) + + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node2"], + }; + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0}, + node3:{id:"node3",x:0,y:-1}, + node4:{id:"node4",x:0,y:-2} + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[ + {id:"link0",source:nodes.node1,target:nodes.node3}, + {id:"link1",source:nodes.node2,target:nodes.node4} + ] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph} + }; + + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + const topNode=getNodeWithMinYInFirstCycleGroup(result); + + // EXPECT + expect(topNode).toEqual("node2"); + + }); + + it('should find top node for a cycle with parents at same levels', async()=>{ + + // MOCK + parentNodeNotInCycleMock.mockReturnValue([ + [],["node3"],["node4"] + ]) + + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node2"], + }; + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0}, + node3:{id:"node3",x:0,y:-1}, + node4:{id:"node4",x:0,y:-1} + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[ + {id:"link0",source:nodes.node1,target:nodes.node3}, + {id:"link1",source:nodes.node2,target:nodes.node4} + ] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph} + }; + + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + const topNode=getNodeWithMinYInFirstCycleGroup(result); + + // EXPECT + expect(topNode).toEqual("node1"); + + }); + + it('should find top node for a cycle with child at different levels', async()=>{ + + // MOCK + childNodeNotInCycleMock.mockReturnValue([ + [],["node3"],["node4"] + ]) + + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node2"], + }; + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0}, + node3:{id:"node3",x:0,y:1}, + node4:{id:"node4",x:0,y:2} + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[ + {id:"link0",source:nodes.node1,target:nodes.node3}, + {id:"link1",source:nodes.node2,target:nodes.node4} + ] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph} + }; + + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + const topNode=getNodeWithMinYInFirstCycleGroup(result); + + // EXPECT + expect(topNode).toEqual("node0"); // the opposite of the bottom node (node2) + + }); + + it('should calculate coordinates for 2 cycles related : as line', async()=>{ + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node2","node3"], + }; + const subgraph2:Subgraph={ + name:"cycle2", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node2","node4"], + }; + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0}, + node3:{id:"node3",x:0,y:0}, + node4:{id:"node4",x:10,y:10} + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph, "cycle2":subgraph2} + }; + + const subgraphNetworkExpected:SubgraphNetwork={"network":{"id":"network", + "nodes":{"node0":{"id":"node0","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node1":{"id":"node1","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node2":{"id":"node2","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node3":{"id":"node3","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node4":{"id":"node4","x":10,"y":10,"metadataLayout":{"isFixed":true}}},"links":[]}, + "networkStyle":{}, + "cycles":{"cycle":{"name":"cycle","type":TypeSubgraph.CYCLE,"nodes":["node0","node1","node2","node3"],"radiusCycle":8,"centroidCycle":{"x":0,"y":0}}, + "cycle2":{"name":"cycle2","type":TypeSubgraph.CYCLE,"nodes":["node0","node1","node2","node4"]}}, + "cyclesGroup":{"cycle_group_0":{"name":"cycle_group_0","nodes":["cycle","cycle2"],"type":TypeSubgraph.CYCLEGROUP,"precalculatedNodesPosition":{"node0":{"x":0,"y":-8},"node1":{"x":8,"y":0},"node2":{"x":0,"y":8},"node3":{"x":-8,"y":0},"node4":{"x":0,"y":0}},"width":0,"height":0,"originalPosition":{"x":0,"y":0}}}} + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + + // EXPECT + expect(result).toEqual(subgraphNetworkExpected); + + }); + + it('should calculate coordinates for 2 cycles related : as line and interval unfixed at the end and beginning of cycle', async()=>{ + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0", "node1", "node2", "node3", "node4"], + }; + const subgraph2:Subgraph={ + name:"cycle2", + type:TypeSubgraph.CYCLE, + nodes:["node6","node0","node1","node5"], + }; + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0}, + node3:{id:"node3",x:0,y:0}, + node4:{id:"node4",x:0,y:0}, + node5:{id:"node5",x:0,y:0}, + node6:{id:"node6",x:0,y:0} + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph, "cycle2":subgraph2} + }; + + const subgraphNetworkExpected:SubgraphNetwork={ + "network": { + "id": "network", + "nodes": { + "node0": { "id": "node0", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle" } }, + "node1": { "id": "node1", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle" } }, + "node2": { "id": "node2", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle" } }, + "node3": { "id": "node3", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle" } }, + "node4": { "id": "node4", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle" } }, + "node5": { "id": "node5", "x": 0, "y": 0, "metadataLayout": { "isFixed": true } }, + "node6": { "id": "node6", "x": 0, "y": 0, "metadataLayout": { "isFixed": true } } + }, + "links": [] + }, + "networkStyle": {}, + "cycles": { + "cycle": { + "name": "cycle", + "type": TypeSubgraph.CYCLE, + "nodes": ["node0", "node1", "node2", "node3", "node4"], + "radiusCycle": 10, + "centroidCycle": { "x": 0, "y": 0 } + }, + "cycle2": { + "name": "cycle2", + "type": TypeSubgraph.CYCLE, + "nodes": ["node6", "node0", "node1", "node5"] + } + }, + "cyclesGroup": { + "cycle_group_0": { + "name": "cycle_group_0", + "nodes": ["cycle", "cycle2"], + "type": TypeSubgraph.CYCLEGROUP, + "precalculatedNodesPosition": { + "node0": { "x": 0, "y": -10 }, + "node1": { "x": 9.51, "y": -3.09 }, + "node2": { "x": 5.88, "y": 8.09 }, + "node3": { "x": -5.88, "y": 8.09 }, + "node4": { "x": -9.51, "y": -3.09 }, + "node5": { "x": 6.34, "y": -5.39 }, + "node6": { "x": 3.17, "y": -7.7 } + }, + "width": 0, + "height": 0, + "originalPosition": { "x": 0, "y": 0 } + } + } + } + + + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + + // EXPECT + expect(result).toEqual(subgraphNetworkExpected); + + }); + + + + it('should calculate coordinates for 2 cycles related : as tangent', async()=>{ + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node2",], + }; + const subgraph2:Subgraph={ + name:"cycle2", + type:TypeSubgraph.CYCLE, + nodes:["node0","node3","node4"], + }; + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0}, + node3:{id:"node3",x:0,y:0}, + node4:{id:"node4",x:0,y:0} + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph, "cycle2":subgraph2} + }; + + const subgraphNetworkExpected:SubgraphNetwork={"network":{"id":"network", + "nodes":{"node0":{"id":"node0","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node1":{"id":"node1","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node2":{"id":"node2","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node3":{"id":"node3","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle2"}}, + "node4":{"id":"node4","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle2"}}}, + "links":[]},"networkStyle":{}, + "cycles":{"cycle":{"name":"cycle","type":TypeSubgraph.CYCLE,"nodes":["node0","node1","node2"],"radiusCycle":6,"centroidCycle":{"x":0,"y":0}}, + "cycle2":{"name":"cycle2","type":TypeSubgraph.CYCLE,"nodes":["node0","node3","node4"],"radiusCycle":6,"centroidCycle":{"x":0,"y":-12}}}, + "cyclesGroup":{"cycle_group_0":{"name":"cycle_group_0","nodes":["cycle","cycle2"],"type":TypeSubgraph.CYCLEGROUP,"precalculatedNodesPosition":{"node0":{"x":0,"y":-6},"node1":{"x":5.2,"y":3},"node2":{"x":-5.2,"y":3},"node3":{"x":-5.2,"y":-15},"node4":{"x":5.2,"y":-15}},"width":0,"height":0,"originalPosition":{"x":0,"y":0}}}} + + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + + // EXPECT + expect(result).toEqual(subgraphNetworkExpected); + + }); + + it('should calculate coordinates when force layout neccessary (case line) : intersection of edges', async()=>{ + + // MOCK + isIntersectionGraphMock.mockReturnValue(true); + + getListNodeLinksForCycleGroupAsArrayMock.mockReturnValue({ + nodes: [ + { id: 'node0', fx: 0, fy: -6 }, + { id: 'node1', fx: 5.2, fy: 3 }, + { id: 'node2', fx: -5.2, fy: 3 }, + { id: 'node3' } + ], + links: [] + }); + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node2"], + }; + const subgraph2:Subgraph={ + name:"cycle2", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node3"], + }; + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0}, + node3:{id:"node3",x:0,y:0}, // will be placed at (34,34) by force + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph, "cycle2":subgraph2} + }; + + const subgraphNetworkExpected:SubgraphNetwork={"network":{"id":"network", + "nodes":{"node0":{"id":"node0","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node1":{"id":"node1","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node2":{"id":"node2","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node3":{"id":"node3","x":0,"y":0,"metadataLayout":{"isFixed":true}}}, + "links":[]}, + "networkStyle":{}, + "cycles":{"cycle":{"name":"cycle","type":TypeSubgraph.CYCLE,"nodes":["node0","node1","node2"],"radiusCycle":6,"centroidCycle":{"x":0,"y":0}}, + "cycle2":{"name":"cycle2","type":TypeSubgraph.CYCLE,"nodes":["node0","node1","node3"]}}, + "cyclesGroup":{"cycle_group_0":{"name":"cycle_group_0","nodes":["cycle","cycle2"],"type":TypeSubgraph.CYCLEGROUP, + "precalculatedNodesPosition":{"node0":{"x":0,"y":-6},"node1":{"x":5.2,"y":3},"node2":{"x":-5.2,"y":3},"node3":{"x":34,"y":34}}, + "width":0,"height":0,"originalPosition":{"x":0,"y":0}}}} + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + + // EXPECT + expect(getListNodeLinksForCycleGroupAsArrayMock).toHaveBeenCalledTimes(1); + expect(result).toEqual(subgraphNetworkExpected); + + }); + + it('should test if force layout used (case line) : node overlap ', async()=>{ + + // MOCK + isOverlapNodesMock.mockReturnValue(true); + + getListNodeLinksForCycleGroupAsArrayMock.mockReturnValue({ + nodes: [ + { id: 'node0', fx: 0, fy: -6 }, + { id: 'node1', fx: 5.2, fy: 3 }, + { id: 'node2', fx: -5.2, fy: 3 }, + { id: 'node3' } + ], + links: [] + }); + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node2"], + }; + const subgraph2:Subgraph={ + name:"cycle2", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node3"], + }; + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0}, + node3:{id:"node3",x:0,y:0}, // will be placed at (34,34) by force + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph, "cycle2":subgraph2} + }; + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + let posNode3:CoordinateNull={x:null,y:null}; + + if(result.cyclesGroup && result[TypeSubgraph.CYCLEGROUP]["cycle_group_0"] + && result.cyclesGroup["cycle_group_0"].precalculatedNodesPosition + && result.cyclesGroup["cycle_group_0"].precalculatedNodesPosition.node3){ + posNode3=result.cyclesGroup["cycle_group_0"].precalculatedNodesPosition.node3; + } + + // EXPECT + expect(posNode3).toBeDefined(); + expect(posNode3.x).toEqual(34); + expect(posNode3.y).toEqual(34); + + }); + + it('should test if force layout used (case line) : node-edge overlap ', async()=>{ + + // MOCK + isOverlapNodesEdgesMock.mockReturnValue(true); + + getListNodeLinksForCycleGroupAsArrayMock.mockReturnValue({ + nodes: [ + { id: 'node0', fx: 0, fy: -6 }, + { id: 'node1', fx: 5.2, fy: 3 }, + { id: 'node2', fx: -5.2, fy: 3 }, + { id: 'node3' } + ], + links: [] + }); + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node2"], + }; + const subgraph2:Subgraph={ + name:"cycle2", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node3"], + }; + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0}, + node3:{id:"node3",x:0,y:0}, // will be placed at (34,34) by force + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph, "cycle2":subgraph2} + }; + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + let posNode3:CoordinateNull={x:null,y:null}; + + if(result.cyclesGroup && result[TypeSubgraph.CYCLEGROUP]["cycle_group_0"] + && result.cyclesGroup["cycle_group_0"].precalculatedNodesPosition + && result.cyclesGroup["cycle_group_0"].precalculatedNodesPosition.node3){ + posNode3=result.cyclesGroup["cycle_group_0"].precalculatedNodesPosition.node3; + } + + // EXPECT + expect(posNode3).toBeDefined(); + expect(posNode3.x).toEqual(34); + expect(posNode3.y).toEqual(34); + + }); + + + it('should calculate coordinates for more complex case : 1 cycle group with 1 line and 1 tangent', async()=>{ + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node6","node7","node8","node9"], + }; + const subgraph2:Subgraph={ + name:"cycle2", + type:TypeSubgraph.CYCLE, + nodes:["node1","node2","node3","node4","node5"], + }; + const subgraph3:Subgraph={ + name:"cycle3", + type:TypeSubgraph.CYCLE, + nodes:["node6","node7","node8","node2"], + }; + + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0}, + node3:{id:"node3",x:0,y:0}, + node4:{id:"node4",x:0,y:0}, + node5:{id:"node5",x:0,y:0}, + node6:{id:"node6",x:0,y:0}, + node7:{id:"node7",x:0,y:0}, + node8:{id:"node8",x:0,y:0}, + node9:{id:"node9",x:0,y:0} + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph, "cycle2":subgraph2, "cycle3":subgraph3} + }; + + const subgraphNetworkExpected:SubgraphNetwork={ + "network": { + "id": "network", + "nodes": { + "node0": { "id": "node0", "x": 0, "y": 0 }, + "node1": { "id": "node1", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle2" } }, + "node2": { "id": "node2", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle2" } }, + "node3": { "id": "node3", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle2" } }, + "node4": { "id": "node4", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle2" } }, + "node5": { "id": "node5", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle2" } }, + "node6": { "id": "node6", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle3" } }, + "node7": { "id": "node7", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle3" } }, + "node8": { "id": "node8", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle3" } }, + "node9": { "id": "node9", "x": 0, "y": 0, "metadataLayout": { "isFixed": true } } + }, + "links": [] + }, + "networkStyle": {}, + "cycles": { + "cycle": { "name": "cycle", "type": TypeSubgraph.CYCLE, "nodes": ["node6", "node7", "node8", "node9"] }, + "cycle2": { "name": "cycle2", "type": TypeSubgraph.CYCLE, "nodes": ["node1", "node2", "node3", "node4", "node5"], "radiusCycle": 10, "centroidCycle": { "x": 0, "y": 0 } }, + "cycle3": { "name": "cycle3", "type": TypeSubgraph.CYCLE, "nodes": ["node6", "node7", "node8", "node2"], "radiusCycle": 8, "centroidCycle": { "x": 17.12, "y": -5.56 } } + }, + "cyclesGroup": { + "cycle_group_0": { + "name": "cycle_group_0", + "nodes": ["cycle2", "cycle3", "cycle"], + "type": TypeSubgraph.CYCLEGROUP, + "precalculatedNodesPosition": { + "node1": { "x": 0, "y": -10 }, + "node2": { "x": 9.51, "y": -3.09 }, + "node3": { "x": 5.88, "y": 8.09 }, + "node4": { "x": -5.88, "y": 8.09 }, + "node5": { "x": -9.51, "y": -3.09 }, + "node6": { "x": 14.65, "y": -13.17 }, + "node7": { "x": 24.73, "y": -8.03 }, + "node8": { "x": 19.59, "y": 2.05 }, + "node9": { "x": 17.12, "y": -5.56 } + }, + "width": 0, + "height": 0, + "originalPosition": { "x": 0, "y": 0 } + } + } + } + + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + + // EXPECT + expect(result).toEqual(subgraphNetworkExpected); + + }); + + + it('should calculate coordinates for 2 cycles independant', async()=>{ + + // DATA + const subgraph:Subgraph={ + name:"cycle", + type:TypeSubgraph.CYCLE, + nodes:["node0","node1","node2"], + }; + const subgraph2:Subgraph={ + name:"cycle2", + type:TypeSubgraph.CYCLE, + nodes:["node3","node4","node5"], + }; + const nodes:{[key:string]:NodeLayout}={ + node0:{id:"node0",x:0,y:0}, + node1:{id:"node1",x:0,y:0}, + node2:{id:"node2",x:0,y:0}, + node3:{id:"node3",x:0,y:0}, + node4:{id:"node4",x:0,y:0}, + node5:{id:"node5",x:0,y:0}, + }; + + const network:NetworkLayout={ + id:"network", + nodes:nodes, + links:[] + }; + + const subgraphNetwork:SubgraphNetwork={ + network:network, + networkStyle:{}, + [TypeSubgraph.CYCLE]:{"cycle":subgraph, "cycle2":subgraph2} + }; + + const subgraphNetworkExpected:SubgraphNetwork={ + "network": { + "id": "network", + "nodes": { + "node0": { "id": "node0", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle" } }, + "node1": { "id": "node1", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle" } }, + "node2": { "id": "node2", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle" } }, + "node3": { "id": "node3", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle2" } }, + "node4": { "id": "node4", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle2" } }, + "node5": { "id": "node5", "x": 0, "y": 0, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle2" } } + }, + "links": [] + }, + "networkStyle": {}, + "cycles": { + "cycle": { "name": "cycle", "type": TypeSubgraph.CYCLE, "nodes": ["node0", "node1", "node2"], "radiusCycle": 6, "centroidCycle": { "x": 0, "y": 0 } }, + "cycle2": { "name": "cycle2", "type": TypeSubgraph.CYCLE, "nodes": ["node3", "node4", "node5"], "radiusCycle": 6, "centroidCycle": { "x": 0, "y": 0 } } + }, + "cyclesGroup": { + "cycle_group_0": { + "name": "cycle_group_0", + "nodes": ["cycle"], + "type": TypeSubgraph.CYCLEGROUP, + "precalculatedNodesPosition": { + "node0": { "x": 0, "y": -6 }, + "node1": { "x": 5.2, "y": 3 }, + "node2": { "x": -5.2, "y": 3 } + }, + "width": 0,"height": 0,"originalPosition": { "x": 0, "y": 0 } + }, + "cycle_group_1": { + "name": "cycle_group_1", + "nodes": ["cycle2"], + "type": TypeSubgraph.CYCLEGROUP, + "precalculatedNodesPosition": { + "node3": { "x": 0, "y": -6 }, + "node4": { "x": 5.2, "y": 3 }, + "node5": { "x": -5.2, "y": 3 } + }, + "width": 0,"height": 0,"originalPosition": { "x": 0, "y": 0 } + } + } + }; + + // TEST + const result=await LayoutDrawCycle.coordinateAllCycles(subgraphNetwork); + + + // EXPECT + expect(getNodesPlacedInGroupCycleAsObjectMock).toHaveBeenCalledTimes(2); + expect(result).toEqual(subgraphNetworkExpected); + + }); + + + }); + + + describe('drawAllCyclesGroup',()=>{ + + it('should draw all cycle group',()=>{ + + // MOCK + getNodesPlacedInGroupCycleAsObjectMock + .mockReturnValueOnce( + { + node0: { x: 0, y: -6 }, + node1: { x: 5.2, y: 3 }, + node2: { x: -5.2, y: 3 } + }) + .mockReturnValueOnce({ node3: { x: 6, y: -6 } }) + + + // DATA + const nodes:{[key:string]:NodeLayout}={"node0":{"id":"node0","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node1":{"id":"node1","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node2":{"id":"node2","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node3":{"id":"node3","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle2"}}}; + + const subgraphNetwork:SubgraphNetwork={ + "network":{ + "id":"network", + nodes:nodes, + "links":[]}, + "networkStyle":{}, + "cyclesGroup":{cycle_group_0: + {"name":"cycle_group_0","nodes":["cycle"], + "type":TypeSubgraph.CYCLEGROUP, + "position":{x:10,y:10}, + "precalculatedNodesPosition":{ + "node0":{"x":0,"y":-6}, + "node1":{"x":5.2,"y":3}, + "node2":{"x":-5.2,"y":3}}, + "width":10.4,"height":9, + "originalPosition":{"x":0,"y":-1.5}}, + cycle_group_1: + {"name":"cycle_group_1","nodes":["cycle2"], + "type":TypeSubgraph.CYCLEGROUP, + "position":{x:-10,y:-10}, + "precalculatedNodesPosition":{ + "node3":{"x":6,"y":-6}}, + "width":10.4,"height":9, + "originalPosition":{"x":0,"y":-1.5}}}}; + + + + const networkExpected : NetworkLayout ={ + "id": "network", + "nodes": { + "node0": { "id": "node0", "x": 10, "y": 5.5, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle" } }, + "node1": { "id": "node1", "x": 15.2, "y": 14.5, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle" } }, + "node2": { "id": "node2", "x": 4.8, "y": 14.5, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle" } }, + "node3": { "id": "node3", "x": -4, "y": -14.5, "metadataLayout": { "isFixed": true, "fixedInCircle": "cycle2" } } + }, + "links": [] + }; + + // TEST + LayoutDrawCycle.drawAllCyclesGroup(subgraphNetwork); + + // EXPECT + expect(subgraphNetwork.network).toEqual(networkExpected); + + }); + + it('should throw error because no original position of cyclegroup',()=>{ + + // MOCK + getNodesPlacedInGroupCycleAsObjectMock + .mockReturnValueOnce( + { + node0: { x: 0, y: -6 }, + node1: { x: 5.2, y: 3 }, + node2: { x: -5.2, y: 3 } + }) + .mockReturnValueOnce({ node3: { x: 6, y: -6 } }) + + + // DATA + const nodes:{[key:string]:NodeLayout}={"node0":{"id":"node0","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node1":{"id":"node1","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node2":{"id":"node2","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + }; + + const subgraphNetwork:SubgraphNetwork={ + "network":{ + "id":"network", + nodes:nodes, + "links":[]}, + "networkStyle":{}, + "cyclesGroup":{cycle_group_0: + {"name":"cycle_group_0","nodes":["cycle"], + "type":TypeSubgraph.CYCLEGROUP, + "position":{x:10,y:10}, + "precalculatedNodesPosition":{ + "node0":{"x":0,"y":-6}, + "node1":{"x":5.2,"y":3}, + "node2":{"x":-5.2,"y":3}}, + "width":10.4,"height":9, + }} + }; + + // TEST & EXPECT + expect(() => { + LayoutDrawCycle.drawAllCyclesGroup(subgraphNetwork); + }).toThrow(); + + }); + + it('should throw error because no position of metanode',()=>{ + + // MOCK + getNodesPlacedInGroupCycleAsObjectMock + .mockReturnValueOnce( + { + node0: { x: 0, y: -6 }, + node1: { x: 5.2, y: 3 }, + node2: { x: -5.2, y: 3 } + }) + .mockReturnValueOnce({ node3: { x: 6, y: -6 } }) + + + // DATA + const nodes:{[key:string]:NodeLayout}={"node0":{"id":"node0","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node1":{"id":"node1","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node2":{"id":"node2","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + }; + + const subgraphNetwork:SubgraphNetwork={ + "network":{ + "id":"network", + nodes:nodes, + "links":[]}, + "networkStyle":{}, + "cyclesGroup":{cycle_group_0: + {"name":"cycle_group_0","nodes":["cycle"], + "type":TypeSubgraph.CYCLEGROUP, + "precalculatedNodesPosition":{ + "node0":{"x":0,"y":-6}, + "node1":{"x":5.2,"y":3}, + "node2":{"x":-5.2,"y":3}}, + "width":10.4,"height":9, + "originalPosition":{"x":0,"y":-1.5} + }} + }; + + // TEST & EXPECT + expect(() => { + LayoutDrawCycle.drawAllCyclesGroup(subgraphNetwork); + }).toThrow(); + + }); + + it('should throw error because node in metadata but not in network',()=>{ + + // MOCK + getNodesPlacedInGroupCycleAsObjectMock + .mockReturnValueOnce( + { + node0: { x: 0, y: -6 }, + node1: { x: 5.2, y: 3 }, + node2: { x: -5.2, y: 3 } + }) + .mockReturnValueOnce({ node3: { x: 6, y: -6 } }) + + + // DATA + const nodes:{[key:string]:NodeLayout}={"node0":{"id":"node0","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + "node1":{"id":"node1","x":0,"y":0,"metadataLayout":{"isFixed":true,"fixedInCircle":"cycle"}}, + }; + + const subgraphNetwork:SubgraphNetwork={ + "network":{ + "id":"network", + nodes:nodes, + "links":[]}, + "networkStyle":{}, + "cyclesGroup":{cycle_group_0: + {"name":"cycle_group_0","nodes":["cycle"], + "type":TypeSubgraph.CYCLEGROUP, + "position":{x:10,y:10}, + "precalculatedNodesPosition":{ + "node0":{"x":0,"y":-6}, + "node1":{"x":5.2,"y":3}, + "node2":{"x":-5.2,"y":3}}, + "width":10.4,"height":9, + "originalPosition":{"x":0,"y":-1.5} + }} + }; + + // TEST & EXPECT + expect(() => { + LayoutDrawCycle.drawAllCyclesGroup(subgraphNetwork); + }).toThrow(); + + }); + + + + }); + +}); \ No newline at end of file -- GitLab