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