From 8482af614b771cd97059f7ff0a896fcec12c1446 Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Tue, 10 Sep 2024 16:34:19 +0200 Subject: [PATCH 01/13] debut test getSetAttributsNodes --- src/composables/GetSetAttributsNodes.ts | 12 +- .../__tests__/GetSetAttributsNodes.spec.ts | 325 +++++++++++++++++- 2 files changed, 326 insertions(+), 11 deletions(-) diff --git a/src/composables/GetSetAttributsNodes.ts b/src/composables/GetSetAttributsNodes.ts index 5da8874..306db75 100644 --- a/src/composables/GetSetAttributsNodes.ts +++ b/src/composables/GetSetAttributsNodes.ts @@ -67,7 +67,7 @@ import { NetworkLayout } from "../types/NetworkLayout"; -const sideCompoundAttribute="isSideCompound"; +export const sideCompoundAttribute="isSideCompound"; /** * Return if the node is declare as side compound @@ -76,7 +76,7 @@ const sideCompoundAttribute="isSideCompound"; * @returns node is a side compound ? */ export function isSideCompound(node:Node):boolean{ - return Boolean(node.metadata && node.metadata[sideCompoundAttribute]); + return Boolean(node.metadata && node.metadata[getAttributSideCompounds()]); } /** @@ -95,7 +95,7 @@ export function getAttributSideCompounds():string{ export function setAsSideCompound(network:Network,nodeID:string):void{ if(!network.nodes[nodeID]) throw new Error("Node not found"); if(!network.nodes[nodeID].metadata) network.nodes[nodeID].metadata={}; - network.nodes[nodeID].metadata[sideCompoundAttribute]=true; + network.nodes[nodeID].metadata[getAttributSideCompounds()]=true; } @@ -104,7 +104,7 @@ export function setAsSideCompound(network:Network,nodeID:string):void{ -const classDuplicate="duplicate"; +export const classDuplicate="duplicate"; /** * Checks if a node in the network is a duplicate. @@ -131,7 +131,7 @@ export function getClassDuplicate():string{ //___________________________________________________2. Reversible __________________________________________________________________________ const classReversible="reversible"; -const reversibleAttribute="reversible"; +export const reversibleAttribute="reversible"; const reactionClass="reaction"; /** @@ -253,7 +253,7 @@ export function inCycle(network: NetworkLayout, idNode: string): boolean { let inCycle:boolean=false; if ( network.nodes[idNode].metadataLayout && TypeSubgraph.CYCLE in network.nodes[idNode].metadataLayout){ const cycles=network.nodes[idNode].metadataLayout[TypeSubgraph.CYCLE]; - if (cycles.length>0) inCycle=true; + if (cycles && cycles.length>0) inCycle=true; } return inCycle; } diff --git a/src/composables/__tests__/GetSetAttributsNodes.spec.ts b/src/composables/__tests__/GetSetAttributsNodes.spec.ts index 99eb479..b69ebb5 100644 --- a/src/composables/__tests__/GetSetAttributsNodes.spec.ts +++ b/src/composables/__tests__/GetSetAttributsNodes.spec.ts @@ -1,16 +1,331 @@ -import "../GetSetAttributsNodes"; +// Type imports +import { Node } from "@metabohub/viz-core/src/types/Node"; +import { Network } from "@metabohub/viz-core/src/types/Network"; + +// Composables imports +import * as GetSetAttributsNodes from "../GetSetAttributsNodes"; + + + describe('GetSetAttributsNodes', () => { - test("name test", () => { + +// 0. Side Compounds ***************************************************************** + + test("getAttributSideCompounds", () => { + + // TEST + const resultNameAttribut=GetSetAttributsNodes.getAttributSideCompounds(); + + // EXPECT + expect(resultNameAttribut).toEqual(GetSetAttributsNodes.sideCompoundAttribute); + + }); + + it("should not be a side compound", () => { // DATA - + const nodeEmpty:Node ={ + id: "node", + x: 10, + y: 10 + }; + const nodeEmptyMetadata:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{} + }; + const nodeNotSideCompound:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{[GetSetAttributsNodes.getAttributSideCompounds()]:false} + }; + // TEST + const resultNodeEmpy=GetSetAttributsNodes.isSideCompound(nodeEmpty); + const resultNodeEmptyMetadata=GetSetAttributsNodes.isSideCompound(nodeEmptyMetadata); + const resultNodeNotSideCompound=GetSetAttributsNodes.isSideCompound(nodeNotSideCompound); + // EXPECT - expect(false).toEqual(true); + expect(resultNodeEmpy).toEqual(false); + expect(resultNodeEmptyMetadata).toEqual(false); + expect(resultNodeNotSideCompound).toEqual(false); + }); - it('should return true', () => { + it("should be a side compound", () => { + // DATA + const nodeSideCompound:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{[GetSetAttributsNodes.getAttributSideCompounds()]:true} + }; + + // TEST + const resultNodeSideCompound=GetSetAttributsNodes.isSideCompound(nodeSideCompound); + + // EXPECT + expect(resultNodeSideCompound).toEqual(true); + + }); + + test("setAsSideCompound", () => { + + // DATA + let network:Network = { + id:"networkTest", + nodes: { + node1 :{ + id: "node1", + x: 10, + y: 10, + }, + node2: { + id: "node2", + x: 20, + y: 20, + metadata: {} + }, + node3: { + id: "node3", + x: 30, + y: 30, + metadata: {[GetSetAttributsNodes.getAttributSideCompounds()]:false as boolean} + }, + node4: { + id: "node4", + x: 40, + y: 40, + metadata: {[GetSetAttributsNodes.getAttributSideCompounds()]:true as boolean} + } + }, + links: [] + }; + + const networkNode1:Network = { + id:"networkTest", + nodes: { + node1 :{ + id: "node1", + x: 10, + y: 10, + metadata: {[GetSetAttributsNodes.getAttributSideCompounds()]:true as boolean} + }, + node2: { + id: "node2", + x: 20, + y: 20, + metadata: {} + }, + node3: { + id: "node3", + x: 30, + y: 30, + metadata: {[GetSetAttributsNodes.getAttributSideCompounds()]:false as boolean} + }, + node4: { + id: "node4", + x: 40, + y: 40, + metadata: {[GetSetAttributsNodes.getAttributSideCompounds()]:true as boolean} + } + }, + links: [] + }; + + + + // TEST & EXPECT + GetSetAttributsNodes.setAsSideCompound(network,"node1"); + expect(network).toEqual(networkNode1); + + // ...???? expect(false).toEqual(true); + + expect(() => { + GetSetAttributsNodes.setAsSideCompound(network, "node5"); + }).toThrow(); + + }); + +// 1. Duplicate ***************************************************************** + + test("getClassDuplicate", () => { + + // TEST + const resultNameClass=GetSetAttributsNodes.getClassDuplicate(); + + // EXPECT + expect(resultNameClass).toEqual(GetSetAttributsNodes.classDuplicate); + + }); + + it("should not be a duplicate", () => { + // DATA + const network:Network = { + id:"networkTest", + nodes: { + node1 :{ + id: "node1", + x: 10, + y: 10, + }, + node2: { + id: "node2", + x: 20, + y: 20, + classes:[] + }, + node3: { + id: "node3", + x: 30, + y: 30, + classes:[GetSetAttributsNodes.getClassDuplicate()+"_test"] + }, + }, + links: [] + }; + + + // TEST + const resultNodeEmpy=GetSetAttributsNodes.isDuplicate(network,"node1"); + const resultNodeEmptyClasses=GetSetAttributsNodes.isDuplicate(network,"node2"); + const resultNodeNotSideCompound=GetSetAttributsNodes.isDuplicate(network,"node3"); + + + // EXPECT + expect(resultNodeEmpy).toEqual(false); + expect(resultNodeEmptyClasses).toEqual(false); + expect(resultNodeNotSideCompound).toEqual(false); + }); + + it("should throw an error because node not in network for checking duplicate", () => { + // DATA + const network:Network = { + id:"networkTest", + nodes: { + node1 :{ + id: "node1", + x: 10, + y: 10, + }, + node2: { + id: "node2", + x: 20, + y: 20, + classes:[] + }, + node3: { + id: "node3", + x: 30, + y: 30, + classes:[GetSetAttributsNodes.getClassDuplicate()+"_test"] + }, + }, + links: [] + }; + + + // TEST & EXPECT + expect(() => { + GetSetAttributsNodes.isDuplicate(network,"node4"); + }).toThrow(); + + }); + + it("should be a duplicate", () => { + // DATA + let network:Network = { + id:"networkTest", + nodes: { + node1: { + id: "node1", + x: 10, + y: 10, + classes:[GetSetAttributsNodes.getClassDuplicate()] + }, + node2: { + id: "node2", + x: 20, + y: 20, + classes:[GetSetAttributsNodes.getClassDuplicate()+"_test"] + }, + node3: { + id: "node3", + x: 30, + y: 30, + classes:[] + }, + }, + links: [] + }; + + const networkExpected:Network = { + id:"networkTest", + nodes: { + node1: { + id: "node1", + x: 10, + y: 10, + classes:[GetSetAttributsNodes.getClassDuplicate()], + metadata: {[GetSetAttributsNodes.reversibleAttribute]:true} + }, + node2: { + id: "node2", + x: 20, + y: 20, + classes:[GetSetAttributsNodes.getClassDuplicate()+"_test"], + }, + node3: { + id: "node3", + x: 30, + y: 30, + classes:[], + }, + }, + links: [] + }; + // TEST + const resultNetwork=GetSetAttributsNodes.addMetadataReversibleWithClass(network); + + // EXPECT + expect(resultNetwork).toEqual(networkExpected); + expect(true).toEqual(false); // pas fait tous les cas + + }); + +// 2. Reversible ***************************************************************** + + test("addMetadataReversibleWithClass", () => { + // DATA + const network:Network = { + id:"networkTest", + nodes: { + node1: { + id: "node1", + x: 10, + y: 10, + classes:[GetSetAttributsNodes.getClassDuplicate()] + }, + node2: { + id: "node2", + x: 20, + y: 20, + classes:[GetSetAttributsNodes.getClassDuplicate(),GetSetAttributsNodes.getClassDuplicate()+"_test"] + }, + node3: { + id: "node3", + x: 30, + y: 30, + classes:[GetSetAttributsNodes.getClassDuplicate()+"_test",GetSetAttributsNodes.getClassDuplicate()] + }, + }, + links: [] + }; + + }); -- GitLab From df4eeef5968e5c165d0fa26f4021c56eccbdd2cd Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Wed, 11 Sep 2024 09:13:51 +0200 Subject: [PATCH 02/13] presque tous les test de getSetAttribut --- src/composables/GetSetAttributsNodes.ts | 4 +- .../__tests__/GetSetAttributsNodes.spec.ts | 531 ++++++++++++++++-- 2 files changed, 473 insertions(+), 62 deletions(-) diff --git a/src/composables/GetSetAttributsNodes.ts b/src/composables/GetSetAttributsNodes.ts index 306db75..348e63c 100644 --- a/src/composables/GetSetAttributsNodes.ts +++ b/src/composables/GetSetAttributsNodes.ts @@ -130,9 +130,9 @@ export function getClassDuplicate():string{ /*******************************************************************************************************************************************************/ //___________________________________________________2. Reversible __________________________________________________________________________ -const classReversible="reversible"; +export const classReversible="reversible"; export const reversibleAttribute="reversible"; -const reactionClass="reaction"; +export const reactionClass="reaction"; /** * Checks if a node in the network is reversible by checking its classes. diff --git a/src/composables/__tests__/GetSetAttributsNodes.spec.ts b/src/composables/__tests__/GetSetAttributsNodes.spec.ts index b69ebb5..f79d71f 100644 --- a/src/composables/__tests__/GetSetAttributsNodes.spec.ts +++ b/src/composables/__tests__/GetSetAttributsNodes.spec.ts @@ -1,6 +1,10 @@ // Type imports import { Node } from "@metabohub/viz-core/src/types/Node"; import { Network } from "@metabohub/viz-core/src/types/Network"; +import { Link } from "@metabohub/viz-core/src/types/Link"; +import { NetworkLayout } from "../../types/NetworkLayout"; +import { TypeSubgraph } from "../../types/Subgraph"; + // Composables imports import * as GetSetAttributsNodes from "../GetSetAttributsNodes"; @@ -8,6 +12,7 @@ import * as GetSetAttributsNodes from "../GetSetAttributsNodes"; + describe('GetSetAttributsNodes', () => { // 0. Side Compounds ***************************************************************** @@ -42,16 +47,36 @@ describe('GetSetAttributsNodes', () => { metadata:{[GetSetAttributsNodes.getAttributSideCompounds()]:false} }; + const nodeNotSideCompound2:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{[GetSetAttributsNodes.getAttributSideCompounds()]:false, + [GetSetAttributsNodes.getAttributSideCompounds()+"_test"]:true + } + }; + + const nodeNotSideCompound3:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{[GetSetAttributsNodes.getAttributSideCompounds()+"_test"]:true} + }; + // TEST const resultNodeEmpy=GetSetAttributsNodes.isSideCompound(nodeEmpty); const resultNodeEmptyMetadata=GetSetAttributsNodes.isSideCompound(nodeEmptyMetadata); const resultNodeNotSideCompound=GetSetAttributsNodes.isSideCompound(nodeNotSideCompound); + const resultNodeNotSideCompound2=GetSetAttributsNodes.isSideCompound(nodeNotSideCompound2); + const resultNodeNotSideCompound3=GetSetAttributsNodes.isSideCompound(nodeNotSideCompound3); // EXPECT expect(resultNodeEmpy).toEqual(false); expect(resultNodeEmptyMetadata).toEqual(false); expect(resultNodeNotSideCompound).toEqual(false); + expect(resultNodeNotSideCompound2).toEqual(false); + expect(resultNodeNotSideCompound3).toEqual(false); }); @@ -64,11 +89,22 @@ describe('GetSetAttributsNodes', () => { metadata:{[GetSetAttributsNodes.getAttributSideCompounds()]:true} }; + const nodeSideCompound2:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{[GetSetAttributsNodes.getAttributSideCompounds()]:true, + [GetSetAttributsNodes.getAttributSideCompounds()+"_test"]:false + } + }; + // TEST const resultNodeSideCompound=GetSetAttributsNodes.isSideCompound(nodeSideCompound); + const resultNodeSideCompound2=GetSetAttributsNodes.isSideCompound(nodeSideCompound2); // EXPECT expect(resultNodeSideCompound).toEqual(true); + expect(resultNodeSideCompound2).toEqual(true); }); @@ -104,47 +140,48 @@ describe('GetSetAttributsNodes', () => { }, links: [] }; - - const networkNode1:Network = { - id:"networkTest", - nodes: { - node1 :{ - id: "node1", - x: 10, - y: 10, - metadata: {[GetSetAttributsNodes.getAttributSideCompounds()]:true as boolean} - }, - node2: { - id: "node2", - x: 20, - y: 20, - metadata: {} - }, - node3: { - id: "node3", - x: 30, - y: 30, - metadata: {[GetSetAttributsNodes.getAttributSideCompounds()]:false as boolean} - }, - node4: { - id: "node4", - x: 40, - y: 40, - metadata: {[GetSetAttributsNodes.getAttributSideCompounds()]:true as boolean} - } - }, - links: [] - }; - - + const initialNetwork:Network = JSON.parse(JSON.stringify(network)); + // TEST & EXPECT - GetSetAttributsNodes.setAsSideCompound(network,"node1"); - expect(network).toEqual(networkNode1); - - // ...???? - expect(false).toEqual(true); - + expect(true).toEqual(false); + // node without metadata + // GetSetAttributsNodes.setAsSideCompound(network,"node1"); + // expect(network.nodes['node1'].metadata).toBeDefined(); + // expect(network.nodes['node1'].metadata).toBeInstanceOf(Object); + // expect(network.nodes['node1'].metadata).toHaveProperty(GetSetAttributsNodes.getAttributSideCompounds()); + // expect(network.nodes['node1'].metadata[GetSetAttributsNodes.getAttributSideCompounds()] as boolean).toEqual(true); + // delete network.nodes['node1'].metadata; + // expect(network).toEqual(initialNetwork); + + + // // node with empty metadata + // GetSetAttributsNodes.setAsSideCompound(network,"node2"); + // expect(network.nodes['node2'].metadata).toBeDefined(); + // expect(network.nodes['node2'].metadata).toBeInstanceOf(Object); + // expect(network.nodes['node2'].metadata).toHaveProperty(GetSetAttributsNodes.getAttributSideCompounds()); + // expect(network.nodes['node2'].metadata[GetSetAttributsNodes.getAttributSideCompounds()] as boolean).toEqual(true); + // delete network.nodes['node2'].metadata[GetSetAttributsNodes.getAttributSideCompounds()]; + // expect(network).toEqual(initialNetwork); + + // // node with false sideCompound + // GetSetAttributsNodes.setAsSideCompound(network,"node3"); + // expect(network.nodes['node3'].metadata).toBeDefined(); + // expect(network.nodes['node3'].metadata).toBeInstanceOf(Object); + // expect(network.nodes['node3'].metadata).toHaveProperty(GetSetAttributsNodes.getAttributSideCompounds()); + // expect(network.nodes['node3'].metadata[GetSetAttributsNodes.getAttributSideCompounds()] as boolean).toEqual(true); + // network.nodes['node3'].metadata[GetSetAttributsNodes.getAttributSideCompounds()]=false; + // expect(network).toEqual(initialNetwork); + + // // node with true sideCompound + // GetSetAttributsNodes.setAsSideCompound(network,"node4"); + // expect(network.nodes['node4'].metadata).toBeDefined(); + // expect(network.nodes['node4'].metadata).toBeInstanceOf(Object); + // expect(network.nodes['node4'].metadata).toHaveProperty(GetSetAttributsNodes.getAttributSideCompounds()); + // expect(network.nodes['node4'].metadata[GetSetAttributsNodes.getAttributSideCompounds()] as boolean).toEqual(true); + // expect(network).toEqual(initialNetwork); + + // node not in network expect(() => { GetSetAttributsNodes.setAsSideCompound(network, "node5"); }).toThrow(); @@ -239,7 +276,7 @@ describe('GetSetAttributsNodes', () => { it("should be a duplicate", () => { // DATA - let network:Network = { + const network:Network = { id:"networkTest", nodes: { node1: { @@ -252,80 +289,454 @@ describe('GetSetAttributsNodes', () => { id: "node2", x: 20, y: 20, - classes:[GetSetAttributsNodes.getClassDuplicate()+"_test"] + classes:[GetSetAttributsNodes.getClassDuplicate(),GetSetAttributsNodes.getClassDuplicate()+"_test"] }, node3: { id: "node3", x: 30, y: 30, - classes:[] + classes:[GetSetAttributsNodes.getClassDuplicate()+"_test",GetSetAttributsNodes.getClassDuplicate()] }, }, links: [] }; - const networkExpected:Network = { + // TEST + const resultNode1=GetSetAttributsNodes.isDuplicate(network,"node1"); + const resultNode2=GetSetAttributsNodes.isDuplicate(network,"node2"); + const resultNode3=GetSetAttributsNodes.isDuplicate(network,"node3"); + + // EXPECT + expect(resultNode1).toEqual(true); + expect(resultNode2).toEqual(true); + expect(resultNode3).toEqual(true); + + }); + +// 2. Reversible ***************************************************************** + + test("addMetadataReversibleWithClass", async () => { + // DATA + let network:Network = { id:"networkTest", nodes: { - node1: { + node1 :{ id: "node1", x: 10, y: 10, - classes:[GetSetAttributsNodes.getClassDuplicate()], - metadata: {[GetSetAttributsNodes.reversibleAttribute]:true} }, node2: { id: "node2", x: 20, y: 20, - classes:[GetSetAttributsNodes.getClassDuplicate()+"_test"], + classes: [GetSetAttributsNodes.classReversible+"_test"] }, node3: { id: "node3", x: 30, y: 30, - classes:[], + classes: [GetSetAttributsNodes.classReversible] }, + node4: { + id: "node4", + x: 40, + y: 40, + classes: [GetSetAttributsNodes.classReversible+"_test",GetSetAttributsNodes.classReversible] + } }, links: [] }; + + let networkExpected:Network = { + id:"networkTest", + nodes: { + node1 :{ + id: "node1", + x: 10, + y: 10, + }, + node2: { + id: "node2", + x: 20, + y: 20, + classes: [GetSetAttributsNodes.classReversible+"_test"] + }, + node3: { + id: "node3", + x: 30, + y: 30, + classes: [GetSetAttributsNodes.classReversible], + metadata: {[GetSetAttributsNodes.reversibleAttribute]:true} + }, + node4: { + id: "node4", + x: 40, + y: 40, + classes: [GetSetAttributsNodes.classReversible+"_test",GetSetAttributsNodes.classReversible], + metadata: {[GetSetAttributsNodes.reversibleAttribute]:true} + } + }, + links: [] + }; + // TEST - const resultNetwork=GetSetAttributsNodes.addMetadataReversibleWithClass(network); + await GetSetAttributsNodes.addMetadataReversibleWithClass(network); // EXPECT - expect(resultNetwork).toEqual(networkExpected); - expect(true).toEqual(false); // pas fait tous les cas + expect(network).toEqual(networkExpected); - }); - -// 2. Reversible ***************************************************************** + }); - test("addMetadataReversibleWithClass", () => { + test("addReversibleNetwork", () => { // DATA - const network:Network = { + let network:Network = { id:"networkTest", nodes: { - node1: { + node1 :{ id: "node1", x: 10, y: 10, - classes:[GetSetAttributsNodes.getClassDuplicate()] }, node2: { id: "node2", x: 20, y: 20, - classes:[GetSetAttributsNodes.getClassDuplicate(),GetSetAttributsNodes.getClassDuplicate()+"_test"] + metadata: {} }, node3: { id: "node3", x: 30, y: 30, - classes:[GetSetAttributsNodes.getClassDuplicate()+"_test",GetSetAttributsNodes.getClassDuplicate()] + metadata: {[GetSetAttributsNodes.reversibleAttribute]:false as boolean} }, + node4: { + id: "node4", + x: 40, + y: 40, + metadata: {[GetSetAttributsNodes.reversibleAttribute]:true as boolean} + } }, links: [] }; + // TEST + + // EXPECT + expect(true).toEqual(false); + + }); + + test("addReversible", () => { + // DATA + const nodeEmpty:Node ={ + id: "node", + x: 10, + y: 10 + }; + const nodeEmptyMetadata:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{} + }; + const nodeNotReversible:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{[GetSetAttributsNodes.reversibleAttribute]:false} + }; + const nodeReversible:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{[GetSetAttributsNodes.reversibleAttribute]:true} + }; + + const nodeExpectedReversible:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{[GetSetAttributsNodes.reversibleAttribute]:true} + }; + + + const nodeNotReversible2:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{[GetSetAttributsNodes.reversibleAttribute]:false, + [GetSetAttributsNodes.reversibleAttribute+"_test"]:false + } + }; + + const nodeReversible2:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{[GetSetAttributsNodes.reversibleAttribute]:true, + [GetSetAttributsNodes.reversibleAttribute+"_test"]:false + } + }; + + const nodeExpectedReversible2:Node ={ + id: "node", + x: 10, + y: 10, + metadata:{[GetSetAttributsNodes.reversibleAttribute]:true, + [GetSetAttributsNodes.reversibleAttribute+"_test"]:false + } + }; + + // TEST + const resultNodeEmpty=GetSetAttributsNodes.addReversible(nodeEmpty); + const resultNodeEmptyMetadata=GetSetAttributsNodes.addReversible(nodeEmptyMetadata); + const resultNodeNotReversible=GetSetAttributsNodes.addReversible(nodeNotReversible); + const resultNodeReversible=GetSetAttributsNodes.addReversible(nodeReversible); + const resultNodeNotReversible2=GetSetAttributsNodes.addReversible(nodeNotReversible2); + const resultNodeReversible2=GetSetAttributsNodes.addReversible(nodeReversible2); + + // EXPECT + expect(resultNodeEmpty).toEqual(nodeExpectedReversible); + expect(resultNodeEmptyMetadata).toEqual(nodeExpectedReversible); + expect(resultNodeNotReversible).toEqual(nodeExpectedReversible); + expect(resultNodeReversible).toEqual(nodeExpectedReversible); + expect(resultNodeNotReversible2).toEqual(nodeExpectedReversible2); + expect(resultNodeReversible2).toEqual(nodeExpectedReversible2); + + }); + + test("addLinkClassReversible", () => { + // DATA + const node1: Node = { + id: "node1", + x: 10, + y: 10 + }; + const node2: Node = { + id: "node2", + x: 20, + y: 20 + }; + const linkEmpty: Link = { + id: "link", + source: node1, + target: node2 + }; + const linkEmptyClasses: Link = { + id: "link", + source: node1, + target: node2, + classes: [] + }; + const linkWithOtherClass: Link = { + id: "link", + source: node1, + target: node2, + classes: [GetSetAttributsNodes.classReversible+"_test"] + }; + const linkWithReversibleClass: Link = { + id: "link", + source: node1, + target: node2, + classes: [GetSetAttributsNodes.classReversible] + }; + + const linkExpectedReversible: Link = { + id: "link", + source: node1, + target: node2, + classes: [GetSetAttributsNodes.classReversible] + }; + + const linkExpectedReversibleWithOtherClass: Link = { + id: "link", + source: node1, + target: node2, + classes: [GetSetAttributsNodes.classReversible+"_test", GetSetAttributsNodes.classReversible] + }; + + // TEST + const resultLinkEmpty = GetSetAttributsNodes.addLinkClassReversible(linkEmpty); + const resultLinkEmptyClasses = GetSetAttributsNodes.addLinkClassReversible(linkEmptyClasses); + const resultLinkWithOtherClass = GetSetAttributsNodes.addLinkClassReversible(linkWithOtherClass); + const resultLinkWithReversibleClass = GetSetAttributsNodes.addLinkClassReversible(linkWithReversibleClass); + + // EXPECT + expect(resultLinkEmpty).toEqual(linkExpectedReversible); + expect(resultLinkEmptyClasses).toEqual(linkExpectedReversible); + expect(resultLinkWithOtherClass).toEqual(linkExpectedReversibleWithOtherClass); + expect(resultLinkWithReversibleClass).toEqual(linkWithReversibleClass); + }); + + test("pushUniqueString", () => { + // DATA + const arrayEmpty: string[] = []; + const arrayWithValues: string[] = ["value1", "value2"]; + const arrayForDuplicate: string[] = ["value1", "value2"]; + + // TEST + const resultEmptyArray = GetSetAttributsNodes.pushUniqueString(arrayEmpty, "value1"); + const resultArrayWithValues = GetSetAttributsNodes.pushUniqueString(arrayWithValues, "value3"); + const resultArrayWithDuplicate = GetSetAttributsNodes.pushUniqueString(arrayForDuplicate, "value1"); + + // EXPECT + expect(resultEmptyArray).toEqual(["value1"]); + expect(resultArrayWithValues).toEqual(["value1", "value2", "value3"]); + expect(resultArrayWithDuplicate).toEqual(["value1", "value2"]); + }); + + test("isReversible", () => { + // DATA + const network: Network = { + id: "networkTest", + nodes: { + node1: { + id: "node1", + x: 10, + y: 10, + metadata: { [GetSetAttributsNodes.reversibleAttribute]: true } + }, + node2: { + id: "node2", + x: 20, + y: 20, + metadata: { [GetSetAttributsNodes.reversibleAttribute]: false } + }, + node3: { + id: "node3", + x: 30, + y: 30, + metadata: {} + }, + node4: { + id: "node4", + x: 40, + y: 40 + } + }, + links: [] + }; + + // TEST & EXPECT + expect(GetSetAttributsNodes.isReversible(network, "node1")).toEqual(true); + expect(GetSetAttributsNodes.isReversible(network, "node2")).toEqual(false); + expect(GetSetAttributsNodes.isReversible(network, "node3")).toEqual(false); + expect(GetSetAttributsNodes.isReversible(network, "node4")).toEqual(false); + + // node not in network + expect(() => { + GetSetAttributsNodes.isReversible(network, "node5"); + }).toThrow(); + }); + + test("isReaction", () => { + // DATA + const nodeWithoutClasses: Node = { + id: "node1", + x: 10, + y: 10 + }; + const nodeWithEmptyClasses: Node = { + id: "node2", + x: 20, + y: 20, + classes: [] + }; + const nodeWithoutReactionClass: Node = { + id: "node3", + x: 30, + y: 30, + classes: [GetSetAttributsNodes.reactionClass + "_test"] + }; + const nodeWithReactionClass: Node = { + id: "node4", + x: 40, + y: 40, + classes: [GetSetAttributsNodes.reactionClass] + }; + const nodeWithMultipleClasses: Node = { + id: "node5", + x: 50, + y: 50, + classes: [GetSetAttributsNodes.reactionClass, GetSetAttributsNodes.reactionClass + "_test"] + }; + + // TEST + const resultNodeWithoutClasses = GetSetAttributsNodes.isReaction(nodeWithoutClasses); + const resultNodeWithEmptyClasses = GetSetAttributsNodes.isReaction(nodeWithEmptyClasses); + const resultNodeWithoutReactionClass = GetSetAttributsNodes.isReaction(nodeWithoutReactionClass); + const resultNodeWithReactionClass = GetSetAttributsNodes.isReaction(nodeWithReactionClass); + const resultNodeWithMultipleClasses = GetSetAttributsNodes.isReaction(nodeWithMultipleClasses); + + // EXPECT + expect(resultNodeWithoutClasses).toEqual(false); + expect(resultNodeWithEmptyClasses).toEqual(false); + expect(resultNodeWithoutReactionClass).toEqual(false); + expect(resultNodeWithReactionClass).toEqual(true); + expect(resultNodeWithMultipleClasses).toEqual(true); + }); + + test("inCycle", () => { + // DATA + const network: NetworkLayout = { + id: "networkTest", + nodes: { + node0: { + id: "node0", + x: 10, + y: 10, + metadataLayout: { + [TypeSubgraph.CYCLE]: ["cycle1","cycle2"] + } + }, + node1: { + id: "node1", + x: 10, + y: 10, + metadataLayout: { + [TypeSubgraph.CYCLE]: ["cycle1"] + } + }, + node2: { + id: "node2", + x: 20, + y: 20, + metadataLayout: { + [TypeSubgraph.CYCLE]: [] + } + }, + node3: { + id: "node3", + x: 30, + y: 30, + metadataLayout: {} + }, + node4: { + id: "node4", + x: 40, + y: 40 + } + }, + links: [] + }; + + // TEST & EXPECT + expect(GetSetAttributsNodes.inCycle(network, "node0")).toEqual(true); + expect(GetSetAttributsNodes.inCycle(network, "node1")).toEqual(true); + expect(GetSetAttributsNodes.inCycle(network, "node2")).toEqual(false); + expect(GetSetAttributsNodes.inCycle(network, "node3")).toEqual(false); + expect(GetSetAttributsNodes.inCycle(network, "node4")).toEqual(false); + + // node not in network + expect(() => { + GetSetAttributsNodes.inCycle(network, "node5"); + }).toThrow(); + }); + + + + + }); + -- GitLab From 94f9ff8ef206f5c766bbf5c8cfc6865d5ba1ce0b Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Wed, 11 Sep 2024 14:40:55 +0200 Subject: [PATCH 03/13] premiere version test GetSetAttributs --- package.json | 8 +- src/composables/CalculateSize.ts | 33 +-- src/composables/GetSetAttributsNodes.ts | 30 +-- src/composables/LayoutManageSideCompounds.ts | 6 +- .../__tests__/CalculateSize.tests.ts | 160 +++++++++++ ....spec.ts => GetSetAttributsNodes.tests.ts} | 249 ++++++++---------- src/types/SubgraphNetwork.ts | 10 +- 7 files changed, 308 insertions(+), 188 deletions(-) create mode 100644 src/composables/__tests__/CalculateSize.tests.ts rename src/composables/__tests__/{GetSetAttributsNodes.spec.ts => GetSetAttributsNodes.tests.ts} (78%) diff --git a/package.json b/package.json index 9638aad..949cd36 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": false, "scripts": { "build": "tsup", - "test": "jest" + "test": "jest --coverage" }, "dependencies": { "@mdi/font": "^7.4.47", @@ -15,6 +15,8 @@ "xml2js": "^0.6.2" }, "devDependencies": { + "@eslint/js": "^9.10.0", + "@types/eslint__js": "^8.42.3", "@types/jest": "^29.5.12", "@types/jsdom": "^21.1.6", "@types/node": "^20.11.14", @@ -24,6 +26,7 @@ "cytoscape-cose-bilkent": "^3.0.0", "cytoscape-fcose": "^2.2.0", "dagrejs": "^0.2.1", + "eslint": "^9.10.0", "graph-data-structure": "^3.5.0", "jest": "^29.7.0", "jsdom": "^24.0.0", @@ -31,7 +34,8 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "tsup": "^8.0.2", - "typescript": "^5.4.5", + "typescript": "^5.6.2", + "typescript-eslint": "^8.5.0", "xml-js": "^1.6.11" }, "main": "./dist/index.js", diff --git a/src/composables/CalculateSize.ts b/src/composables/CalculateSize.ts index 4a8f814..b0e3b9c 100644 --- a/src/composables/CalculateSize.ts +++ b/src/composables/CalculateSize.ts @@ -10,7 +10,6 @@ import { GraphStyleProperties } from "@metabohub/viz-core/src/types/GraphStylePr import { inCycle, isSideCompound } from "./GetSetAttributsNodes"; // General imports -import { e } from "vitest/dist/reporters-1evA5lom"; @@ -92,7 +91,7 @@ const defaultWidthNode = 25; * @returns The converted value in inches. */ export function pixelsToInches(pixels: number, dpi: number = 72): number { - if (!pixels) throw new Error("No pixels to convert to inches"); + if (dpi===0) throw new Error("DPI value cannot be 0"); return parseFloat((pixels / dpi).toFixed(2)); } @@ -114,23 +113,17 @@ export function inchesToPixels(inches: number, dpi: number = 72): number { * @returns An object containing the height and width of the node in pixels. */ export function getSizeNodePixel(node:Node,styleNetwork:GraphStyleProperties):Size{ - let height:number; - let width:number; - if (node.classes && styleNetwork.nodeStyles){ + let height:number=defaultHeightNode; + let width:number=defaultWidthNode; + if (node.classes){ node.classes.forEach((classe)=>{ - if (classe in styleNetwork.nodeStyles){; + if (styleNetwork.nodeStyles && classe in styleNetwork.nodeStyles){; const style=styleNetwork.nodeStyles[classe]; height = style.height? style.height:height; width = style.width? style.width:width; } }); } - if (!height){ - height = defaultHeightNode; - } - if (!width){ - width = defaultWidthNode; - } return {height:height,width:width}; } @@ -349,9 +342,11 @@ export function rectangleSize(listCoordinates:Coordinate[],listID?:string[],subg * @returns The subgraphNetwork with the size of all group cycles calculated. */ export function getSizeAllGroupCycles(subgraphNetwork:SubgraphNetwork):SubgraphNetwork{ - Object.values(subgraphNetwork[TypeSubgraph.CYCLEGROUP]).forEach(groupCycle => { - subgraphNetwork=getSizeGroupCycles(subgraphNetwork,groupCycle); - }); + if (subgraphNetwork[TypeSubgraph.CYCLEGROUP]){ + Object.values(subgraphNetwork[TypeSubgraph.CYCLEGROUP]).forEach(groupCycle => { + subgraphNetwork=getSizeGroupCycles(subgraphNetwork,groupCycle); + }); + } return subgraphNetwork; } @@ -364,12 +359,12 @@ export function getSizeAllGroupCycles(subgraphNetwork:SubgraphNetwork):SubgraphN * @returns The subgraphNetwork with the size of the group cycle calculated. */ function getSizeGroupCycles(subgraphNetwork:SubgraphNetwork,groupCycle:Subgraph):SubgraphNetwork{ - if (groupCycle.metadata){ + if (groupCycle.precalculatedNodesPosition){ // get all nodes with x and y coordinates console.warn('modifier getSizeGroupCycle avec nouveau attribut position'); - const listNodesMetadata = Object.entries(groupCycle.metadata) - .filter(([_,item]) => item["x"] !== undefined && item["y"] !== undefined); - const listCoordinates = listNodesMetadata.map(([_,item]) => {return {x:item["x"],y:item["y"]}}); + const listNodesMetadata = Object.entries(groupCycle.precalculatedNodesPosition) + .filter(([_,item]) => item.x !== undefined && item.y !== undefined); + const listCoordinates = listNodesMetadata.map(([_,item]) => {return {x:item.x,y:item.y}}); const listID = listNodesMetadata.map(([id,_]) => {return id}); // get the size of the rectangle const {width,height,center}=rectangleSize(listCoordinates,listID,subgraphNetwork); diff --git a/src/composables/GetSetAttributsNodes.ts b/src/composables/GetSetAttributsNodes.ts index 348e63c..711f6d3 100644 --- a/src/composables/GetSetAttributsNodes.ts +++ b/src/composables/GetSetAttributsNodes.ts @@ -14,9 +14,6 @@ import { NetworkLayout } from "../types/NetworkLayout"; * -> isSideCompound : * Return if the node is declare as side compound * - * -> getAttributSideCompounds : - * Returns the attribut for side compound nodes. - * * -> setAsSideCompound : * Set the node as side compound * @@ -27,8 +24,6 @@ import { NetworkLayout } from "../types/NetworkLayout"; * -> isDuplicate : * Checks if a node in the network is a duplicate. * - * -> getClassDuplicate : - * Returns the class name for duplicated nodes. * * ********************************* * 2. Reversible @@ -76,16 +71,9 @@ export const sideCompoundAttribute="isSideCompound"; * @returns node is a side compound ? */ export function isSideCompound(node:Node):boolean{ - return Boolean(node.metadata && node.metadata[getAttributSideCompounds()]); + return Boolean(node.metadata && node.metadata[sideCompoundAttribute]); } -/** - * Returns the attribut for side compound nodes. - * @returns the attribut for side compound nodes - */ -export function getAttributSideCompounds():string{ - return sideCompoundAttribute; -} /** * Set the node as side compound @@ -95,7 +83,7 @@ export function getAttributSideCompounds():string{ export function setAsSideCompound(network:Network,nodeID:string):void{ if(!network.nodes[nodeID]) throw new Error("Node not found"); if(!network.nodes[nodeID].metadata) network.nodes[nodeID].metadata={}; - network.nodes[nodeID].metadata[getAttributSideCompounds()]=true; + network.nodes[nodeID].metadata[sideCompoundAttribute]=true; } @@ -117,14 +105,6 @@ export function isDuplicate(network:Network,nodeID:string):boolean{ return Boolean(network.nodes[nodeID].classes && network.nodes[nodeID].classes.includes(classDuplicate)); } -/** - * Returns the class name for duplicated nodes. - * @returns the class name for duplicate nodes - */ -export function getClassDuplicate():string{ - return classDuplicate; -} - /*******************************************************************************************************************************************************/ @@ -158,10 +138,7 @@ export function addReversibleNetwork(network:Network,nodeID:string):void{ if (!network.nodes[nodeID]){ throw new Error("Node not found to set as reversible "); } - if(!network.nodes[nodeID].metadata){ - network.nodes[nodeID].metadata={}; - } - network.nodes[nodeID].metadata[reversibleAttribute]=true; + network.nodes[nodeID]=addReversible(network.nodes[nodeID]); } /** @@ -249,7 +226,6 @@ export function inCycle(network: NetworkLayout, idNode: string): boolean { throw new Error("Node not found in network"); } // no metadata or no cycle metadata or empty cycle metadata : that is, not in a cycle - console.warn("change matadatlayout inCycle"); let inCycle:boolean=false; if ( network.nodes[idNode].metadataLayout && TypeSubgraph.CYCLE in network.nodes[idNode].metadataLayout){ const cycles=network.nodes[idNode].metadataLayout[TypeSubgraph.CYCLE]; diff --git a/src/composables/LayoutManageSideCompounds.ts b/src/composables/LayoutManageSideCompounds.ts index 95819f7..3422389 100644 --- a/src/composables/LayoutManageSideCompounds.ts +++ b/src/composables/LayoutManageSideCompounds.ts @@ -11,7 +11,7 @@ import { GraphStyleProperties } from "@metabohub/viz-core/src/types/GraphStylePr import { getContentFromURL } from "./importNetwork"; import { removeAllSelectedNodes , duplicateAllNodesByAttribut} from "@metabohub/viz-core"; import { getMeanNodesSizePixel, inchesToPixels, minEdgeLength as minEdgeLength, pixelsToInches } from "./CalculateSize"; -import { getAttributSideCompounds, isDuplicate, isReaction, isSideCompound, setAsSideCompound } from "./GetSetAttributsNodes"; +import { sideCompoundAttribute,isDuplicate, isReaction, isSideCompound, setAsSideCompound } from "./GetSetAttributsNodes"; // General imports import { e, S } from "vitest/dist/reporters-1evA5lom"; @@ -145,7 +145,7 @@ import { error } from "console"; * @param network * @param pathListSideCompounds path to the list of id of side compounds */ -export async function addSideCompoundAttributeFromList(subgraphNetwork:SubgraphNetwork, pathListSideCompounds):Promise<void>{ +export async function addSideCompoundAttributeFromList(subgraphNetwork:SubgraphNetwork, pathListSideCompounds:string):Promise<void>{ const listIDSideCompounds = await getIDSideCompoundsInNetworkFromFile(subgraphNetwork,pathListSideCompounds); listIDSideCompounds.forEach((sideCompoundID) => { setAsSideCompound(subgraphNetwork.network.value,sideCompoundID); @@ -237,7 +237,7 @@ export async function duplicateSideCompound(subgraphNetwork:SubgraphNetwork):Pro const network = subgraphNetwork.network.value; const networkStyle = subgraphNetwork.networkStyle.value; // duplication of side compounds - duplicateAllNodesByAttribut(network, networkStyle, getAttributSideCompounds()); + duplicateAllNodesByAttribut(network, networkStyle, sideCompoundAttribute); // add attributes to side compounds duplicates sideCompoundAttributeOnDuplicate(subgraphNetwork); } diff --git a/src/composables/__tests__/CalculateSize.tests.ts b/src/composables/__tests__/CalculateSize.tests.ts new file mode 100644 index 0000000..1523eeb --- /dev/null +++ b/src/composables/__tests__/CalculateSize.tests.ts @@ -0,0 +1,160 @@ +// // Type imports +// import { Node} from '@metabohub/viz-core/src/types/Node'; +// import { Network } from '@metabohub/viz-core/src/types/Network'; +// import { GraphStyleProperties } from '@metabohub/viz-core/src/types/GraphStyleProperties'; + +// // Composable imports +// import { pixelsToInches, inchesToPixels, getSizeNodePixel, +// getMeanNodesSizePixel, getSepAttributesInches, getSepAttributesPixel, +// minEdgeLength, medianEdgeLength, rectangleSize } from '../CalculateSize'; + +// describe('CalculateSize', () => { +// const mockNodeClass1: Node = { +// id: '1', +// classes: ['class1'], +// x: 0, +// y: 0 +// }; +// const mockNodeClass2: Node = { +// id: '2', +// classes: ['class2'], +// x: 0, +// y: 0 +// }; +// const mockNodeClass12: Node = { +// id: '3', +// classes: ['class1','class2'], +// x: 0, +// y: 0 +// }; +// const mockNodeClass21: Node = { +// id: '4', +// classes: ['class2','class1'], +// x: 0, +// y: 0 +// }; + +// const mockNodeClass3: Node = { +// id: '4', +// classes: ['class3'], +// x: 0, +// y: 0 +// }; + +// const mockNodeClass23: Node = { +// id: '4', +// classes: ['class2','class3'], +// x: 0, +// y: 0 +// }; + +// const mockNodeClassEmpty: Node = { +// id: '4', +// classes: [], +// x: 0, +// y: 0 +// }; + +// const mockNodeNoClass: Node = { +// id: '4', +// x: 0, +// y: 0 +// }; + + +// const mockStyle: GraphStyleProperties = { +// nodeStyles: { +// class1: { +// height: 50, +// width: 50 +// }, +// class2: { +// height: 25, +// width: 25 +// } +// } +// }; + +// const mockStyleEmpty: GraphStyleProperties = {}; + +// const mockStyleEmptyNodeStyle: GraphStyleProperties = { +// nodeStyles: {} +// }; + +// const mockNetwork: Network = { +// id: 'network', +// nodes: { +// '1': mockNodeClass1, +// '2': mockNodeClass2, +// '3': mockNodeClass12, +// '4': mockNodeClass21 +// }, +// links: [ +// { id:"link", source: mockNodeClass1, target: { ...mockNodeClass1, id: '2', x: 100, y: 100 } } +// ] +// }; + +// it('should convert pixels to inches', () => { +// expect(pixelsToInches(96)).toBe(1.33); +// }); + +// it('should convert inches to pixels', () => { +// expect(inchesToPixels(1)).toBe(72); +// }); + +// test('getSizeNodePixel', () => { +// // TEST +// const size1 = getSizeNodePixel(mockNodeClass1, mockStyle); +// const size2 = getSizeNodePixel(mockNodeClass2, mockStyle); +// const size12 = getSizeNodePixel(mockNodeClass12, mockStyle); +// const size21 = getSizeNodePixel(mockNodeClass21, mockStyle); +// const size3 = getSizeNodePixel(mockNodeClass3, mockStyle); +// const size23 = getSizeNodePixel(mockNodeClass23, mockStyle); +// const sizeEmpty = getSizeNodePixel(mockNodeClassEmpty, mockStyle); +// const sizeNoClass = getSizeNodePixel(mockNodeNoClass, mockStyle); + +// const sizeNoStyle= getSizeNodePixel(mockNodeClass1, mockStyleEmpty); +// const sizeEmptyStyle = getSizeNodePixel(mockNodeClass1, mockStyleEmptyNodeStyle); + +// // EXPECT +// expect(size).toEqual({ height: 50, width: 50 }); +// }); + +// it('should calculate the mean size of nodes in pixels', async () => { +// const size = await getMeanNodesSizePixel(Object.values(mockNetwork.nodes), mockStyle); +// expect(size).toEqual({ height: 50, width: 50 }); +// }); + +// it('should calculate the rank and node separation in inches', async () => { +// const sep = await getSepAttributesInches(mockNetwork, mockStyle); +// expect(sep).toEqual({ rankSep: 0.69, nodeSep: 0.69 }); +// }); + +// it('should calculate the rank and node separation in pixels', async () => { +// const sep = await getSepAttributesPixel(mockNetwork, mockStyle); +// expect(sep).toEqual({ rankSep: 50, nodeSep: 50 }); +// }); + +// it('should calculate the minimum edge length', () => { +// const minLength = minEdgeLength(mockNetwork); +// expect(minLength).toBe(141.42); +// }); + +// it('should calculate the median edge length', () => { +// const medianLength = medianEdgeLength(mockNetwork); +// expect(medianLength).toBe(141.42); +// }); + +// it('should calculate the size and center coordinates of a rectangle', () => { +// const coordinates: Coordinate[] = [ +// { x: 0, y: 0 }, +// { x: 100, y: 100 } +// ]; +// const size = rectangleSize(coordinates); +// expect(size).toEqual({ +// width: 100, +// height: 100, +// center: { x: 50, y: 50 } +// }); +// }); +// }); \ No newline at end of file diff --git a/src/composables/__tests__/GetSetAttributsNodes.spec.ts b/src/composables/__tests__/GetSetAttributsNodes.tests.ts similarity index 78% rename from src/composables/__tests__/GetSetAttributsNodes.spec.ts rename to src/composables/__tests__/GetSetAttributsNodes.tests.ts index f79d71f..34ccdd2 100644 --- a/src/composables/__tests__/GetSetAttributsNodes.spec.ts +++ b/src/composables/__tests__/GetSetAttributsNodes.tests.ts @@ -17,15 +17,6 @@ describe('GetSetAttributsNodes', () => { // 0. Side Compounds ***************************************************************** - test("getAttributSideCompounds", () => { - - // TEST - const resultNameAttribut=GetSetAttributsNodes.getAttributSideCompounds(); - - // EXPECT - expect(resultNameAttribut).toEqual(GetSetAttributsNodes.sideCompoundAttribute); - - }); it("should not be a side compound", () => { // DATA @@ -44,15 +35,15 @@ describe('GetSetAttributsNodes', () => { id: "node", x: 10, y: 10, - metadata:{[GetSetAttributsNodes.getAttributSideCompounds()]:false} + metadata:{[GetSetAttributsNodes.sideCompoundAttribute]:false} }; const nodeNotSideCompound2:Node ={ id: "node", x: 10, y: 10, - metadata:{[GetSetAttributsNodes.getAttributSideCompounds()]:false, - [GetSetAttributsNodes.getAttributSideCompounds()+"_test"]:true + metadata:{[GetSetAttributsNodes.sideCompoundAttribute]:false, + [GetSetAttributsNodes.sideCompoundAttribute+"_test"]:true } }; @@ -60,7 +51,7 @@ describe('GetSetAttributsNodes', () => { id: "node", x: 10, y: 10, - metadata:{[GetSetAttributsNodes.getAttributSideCompounds()+"_test"]:true} + metadata:{[GetSetAttributsNodes.sideCompoundAttribute+"_test"]:true} }; // TEST @@ -86,15 +77,15 @@ describe('GetSetAttributsNodes', () => { id: "node", x: 10, y: 10, - metadata:{[GetSetAttributsNodes.getAttributSideCompounds()]:true} + metadata:{[GetSetAttributsNodes.sideCompoundAttribute]:true} }; const nodeSideCompound2:Node ={ id: "node", x: 10, y: 10, - metadata:{[GetSetAttributsNodes.getAttributSideCompounds()]:true, - [GetSetAttributsNodes.getAttributSideCompounds()+"_test"]:false + metadata:{[GetSetAttributsNodes.sideCompoundAttribute]:true, + [GetSetAttributsNodes.sideCompoundAttribute+"_test"]:false } }; @@ -109,96 +100,92 @@ describe('GetSetAttributsNodes', () => { }); test("setAsSideCompound", () => { + // DATA + let networkWithoutMetadata: Network = { + nodes: { + node1: { + id: 'node1', + x: 10, + y: 10 + } + }, + links: [], + id: 'test' + } - // DATA - let network:Network = { - id:"networkTest", + let networkWithEmptyMetadata: Network = { nodes: { - node1 :{ - id: "node1", + node1: { + id: 'node1', x: 10, y: 10, - }, - node2: { - id: "node2", - x: 20, - y: 20, metadata: {} - }, - node3: { - id: "node3", - x: 30, - y: 30, - metadata: {[GetSetAttributsNodes.getAttributSideCompounds()]:false as boolean} - }, - node4: { - id: "node4", - x: 40, - y: 40, - metadata: {[GetSetAttributsNodes.getAttributSideCompounds()]:true as boolean} } }, - links: [] - }; - const initialNetwork:Network = JSON.parse(JSON.stringify(network)); - + links: [], + id: 'test' + } - // TEST & EXPECT - expect(true).toEqual(false); - // node without metadata - // GetSetAttributsNodes.setAsSideCompound(network,"node1"); - // expect(network.nodes['node1'].metadata).toBeDefined(); - // expect(network.nodes['node1'].metadata).toBeInstanceOf(Object); - // expect(network.nodes['node1'].metadata).toHaveProperty(GetSetAttributsNodes.getAttributSideCompounds()); - // expect(network.nodes['node1'].metadata[GetSetAttributsNodes.getAttributSideCompounds()] as boolean).toEqual(true); - // delete network.nodes['node1'].metadata; - // expect(network).toEqual(initialNetwork); - - - // // node with empty metadata - // GetSetAttributsNodes.setAsSideCompound(network,"node2"); - // expect(network.nodes['node2'].metadata).toBeDefined(); - // expect(network.nodes['node2'].metadata).toBeInstanceOf(Object); - // expect(network.nodes['node2'].metadata).toHaveProperty(GetSetAttributsNodes.getAttributSideCompounds()); - // expect(network.nodes['node2'].metadata[GetSetAttributsNodes.getAttributSideCompounds()] as boolean).toEqual(true); - // delete network.nodes['node2'].metadata[GetSetAttributsNodes.getAttributSideCompounds()]; - // expect(network).toEqual(initialNetwork); - - // // node with false sideCompound - // GetSetAttributsNodes.setAsSideCompound(network,"node3"); - // expect(network.nodes['node3'].metadata).toBeDefined(); - // expect(network.nodes['node3'].metadata).toBeInstanceOf(Object); - // expect(network.nodes['node3'].metadata).toHaveProperty(GetSetAttributsNodes.getAttributSideCompounds()); - // expect(network.nodes['node3'].metadata[GetSetAttributsNodes.getAttributSideCompounds()] as boolean).toEqual(true); - // network.nodes['node3'].metadata[GetSetAttributsNodes.getAttributSideCompounds()]=false; - // expect(network).toEqual(initialNetwork); - - // // node with true sideCompound - // GetSetAttributsNodes.setAsSideCompound(network,"node4"); - // expect(network.nodes['node4'].metadata).toBeDefined(); - // expect(network.nodes['node4'].metadata).toBeInstanceOf(Object); - // expect(network.nodes['node4'].metadata).toHaveProperty(GetSetAttributsNodes.getAttributSideCompounds()); - // expect(network.nodes['node4'].metadata[GetSetAttributsNodes.getAttributSideCompounds()] as boolean).toEqual(true); - // expect(network).toEqual(initialNetwork); + let networkWithMetadataFalse: Network = { + nodes: { + node1: { + id: 'node1', + x: 10, + y: 10, + metadata: {[GetSetAttributsNodes.sideCompoundAttribute]: false} + } + }, + links: [], + id: 'test' + } + let networkWithMetadataTrue: Network = { + nodes: { + node1: { + id: 'node1', + x: 10, + y: 10, + metadata: {[GetSetAttributsNodes.sideCompoundAttribute]: true} + } + }, + links: [], + id: 'test' + } + + const mockNetwork: Network = { + nodes: { + node1: { + id: 'node1', + x: 10, + y: 10, + metadata: {[GetSetAttributsNodes.sideCompoundAttribute]: true} + } + }, + links: [], + id: 'test' + } + + // TEST + GetSetAttributsNodes.setAsSideCompound(networkWithoutMetadata, "node1") + GetSetAttributsNodes.setAsSideCompound(networkWithEmptyMetadata, "node1") + GetSetAttributsNodes.setAsSideCompound(networkWithMetadataFalse, "node1") + GetSetAttributsNodes.setAsSideCompound(networkWithMetadataTrue, "node1") + + // EXPECT + expect(networkWithoutMetadata).toEqual(mockNetwork); + expect(networkWithEmptyMetadata).toEqual(mockNetwork); + expect(networkWithMetadataFalse).toEqual(mockNetwork); + expect(networkWithMetadataTrue).toEqual(mockNetwork); + // node not in network expect(() => { - GetSetAttributsNodes.setAsSideCompound(network, "node5"); + GetSetAttributsNodes.setAsSideCompound(networkWithMetadataFalse, "node5"); }).toThrow(); }); // 1. Duplicate ***************************************************************** - test("getClassDuplicate", () => { - - // TEST - const resultNameClass=GetSetAttributsNodes.getClassDuplicate(); - - // EXPECT - expect(resultNameClass).toEqual(GetSetAttributsNodes.classDuplicate); - - }); it("should not be a duplicate", () => { // DATA @@ -220,7 +207,7 @@ describe('GetSetAttributsNodes', () => { id: "node3", x: 30, y: 30, - classes:[GetSetAttributsNodes.getClassDuplicate()+"_test"] + classes:[GetSetAttributsNodes.classDuplicate+"_test"] }, }, links: [] @@ -249,19 +236,7 @@ describe('GetSetAttributsNodes', () => { id: "node1", x: 10, y: 10, - }, - node2: { - id: "node2", - x: 20, - y: 20, - classes:[] - }, - node3: { - id: "node3", - x: 30, - y: 30, - classes:[GetSetAttributsNodes.getClassDuplicate()+"_test"] - }, + } }, links: [] }; @@ -283,19 +258,19 @@ describe('GetSetAttributsNodes', () => { id: "node1", x: 10, y: 10, - classes:[GetSetAttributsNodes.getClassDuplicate()] + classes:[GetSetAttributsNodes.classDuplicate] }, node2: { id: "node2", x: 20, y: 20, - classes:[GetSetAttributsNodes.getClassDuplicate(),GetSetAttributsNodes.getClassDuplicate()+"_test"] + classes:[GetSetAttributsNodes.classDuplicate,GetSetAttributsNodes.classDuplicate+"_test"] }, node3: { id: "node3", x: 30, y: 30, - classes:[GetSetAttributsNodes.getClassDuplicate()+"_test",GetSetAttributsNodes.getClassDuplicate()] + classes:[GetSetAttributsNodes.classDuplicate+"_test",GetSetAttributsNodes.classDuplicate] }, }, links: [] @@ -316,7 +291,9 @@ describe('GetSetAttributsNodes', () => { // 2. Reversible ***************************************************************** test("addMetadataReversibleWithClass", async () => { - // DATA + + // DATA + let network:Network = { id:"networkTest", nodes: { @@ -389,40 +366,54 @@ describe('GetSetAttributsNodes', () => { test("addReversibleNetwork", () => { // DATA - let network:Network = { + + let networkNodeNoReversible:Network = { id:"networkTest", nodes: { - node1 :{ - id: "node1", - x: 10, - y: 10, - }, - node2: { - id: "node2", - x: 20, - y: 20, - metadata: {} - }, - node3: { - id: "node3", - x: 30, - y: 30, + node: { + id: "node", + x: 40, + y: 40, metadata: {[GetSetAttributsNodes.reversibleAttribute]:false as boolean} }, - node4: { - id: "node4", + node1: { + id: "node", + x: 40, + y: 40, + metadata: {[GetSetAttributsNodes.reversibleAttribute]:false as boolean} + } + }, + links: [] + }; + + let networkexpected:Network = { + id:"networkTest", + nodes: { + node: { + id: "node", x: 40, y: 40, metadata: {[GetSetAttributsNodes.reversibleAttribute]:true as boolean} + }, + node1: { + id: "node", + x: 40, + y: 40, + metadata: {[GetSetAttributsNodes.reversibleAttribute]:false as boolean} } }, links: [] }; // TEST + GetSetAttributsNodes.addReversibleNetwork(networkNodeNoReversible,"node"); // EXPECT - expect(true).toEqual(false); + expect(networkNodeNoReversible).toEqual(networkexpected); + + expect(() => { + GetSetAttributsNodes.addReversibleNetwork(networkNodeNoReversible,"node2"); + }).toThrow(); }); @@ -732,11 +723,5 @@ describe('GetSetAttributsNodes', () => { GetSetAttributsNodes.inCycle(network, "node5"); }).toThrow(); }); - - - - - - }); diff --git a/src/types/SubgraphNetwork.ts b/src/types/SubgraphNetwork.ts index f774499..bd52a5d 100644 --- a/src/types/SubgraphNetwork.ts +++ b/src/types/SubgraphNetwork.ts @@ -1,6 +1,6 @@ import { Network } from "@metabohub/viz-core/src/types/Network"; import { Node } from "@metabohub/viz-core/src/types/Node"; -import { Subgraph } from "./Subgraph"; +import { Subgraph, TypeSubgraph } from "./Subgraph"; import { Ref } from "vue"; import { GraphStyleProperties } from "@metabohub/viz-core/src/types/GraphStyleProperties"; import { NetworkLayout } from "./NetworkLayout"; @@ -17,17 +17,17 @@ export interface SubgraphNetwork { attributs?: AttributesViz; - mainChains?: { + [TypeSubgraph.MAIN_CHAIN]?: { [key: string]: Subgraph } - secondaryChains?:{ + [TypeSubgraph.SECONDARY_CHAIN]?:{ [key: string]: Subgraph } - cycles?:{ + [TypeSubgraph.CYCLE]?:{ [key:string]:Subgraph } // The cycle metanode : - cyclesGroup?:{ + [TypeSubgraph.CYCLEGROUP]?:{ [key:string]:Subgraph } -- GitLab From db57fe8ad99b43f09ac9722789015206ac92a728 Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Wed, 11 Sep 2024 16:06:27 +0200 Subject: [PATCH 04/13] debut debug calculate size --- src/composables/CalculateSize.ts | 17 +- .../__tests__/CalculateSize.tests.ts | 475 ++++++++++++------ 2 files changed, 323 insertions(+), 169 deletions(-) diff --git a/src/composables/CalculateSize.ts b/src/composables/CalculateSize.ts index b0e3b9c..d4e87da 100644 --- a/src/composables/CalculateSize.ts +++ b/src/composables/CalculateSize.ts @@ -80,8 +80,8 @@ import { inCycle, isSideCompound } from "./GetSetAttributsNodes"; //___________________________________________________0. Nodes __________________________________________________________________________ -const defaultHeightNode = 25; -const defaultWidthNode = 25; +export const defaultHeightNode = 25; +export const defaultWidthNode = 25; /** @@ -102,7 +102,6 @@ export function pixelsToInches(pixels: number, dpi: number = 72): number { * @returns The converted value in pixels. */ export function inchesToPixels(inches: number, dpi: number = 72): number { - if (!inches) throw new Error("No inches to convert to pixels"); return parseFloat((inches * dpi).toFixed(2)); } @@ -206,19 +205,19 @@ export function minEdgeLength(network: Network,cycleInclude:boolean=true): numbe let minDistance = Infinity; network.links.forEach((link) => { if (cycleInclude || (!inCycle(network,link.target.id) || !inCycle(network,link.source.id)) ){ - const dx = link.source.x - link.target.x; - const dy = link.source.y - link.target.y; - const distance = Math.sqrt(dx * dx + dy * dy); - if (distance !==null && distance !== undefined && !isNaN(distance)){ + const dx:number = link.source.x - link.target.x; + const dy:number = link.source.y - link.target.y; + const distance:number = Math.sqrt(dx * dx + dy * dy); + if (isFinite(distance)){ minDistance = Math.min(minDistance, distance); } } }); - minDistance= parseFloat(minDistance.toFixed(2)); if(!isFinite(minDistance)){ return NaN; + }else{ + return parseFloat(minDistance.toFixed(2)); } - return minDistance; } /** diff --git a/src/composables/__tests__/CalculateSize.tests.ts b/src/composables/__tests__/CalculateSize.tests.ts index 1523eeb..03ab43b 100644 --- a/src/composables/__tests__/CalculateSize.tests.ts +++ b/src/composables/__tests__/CalculateSize.tests.ts @@ -1,160 +1,315 @@ -// // Type imports -// import { Node} from '@metabohub/viz-core/src/types/Node'; -// import { Network } from '@metabohub/viz-core/src/types/Network'; -// import { GraphStyleProperties } from '@metabohub/viz-core/src/types/GraphStyleProperties'; - -// // Composable imports -// import { pixelsToInches, inchesToPixels, getSizeNodePixel, -// getMeanNodesSizePixel, getSepAttributesInches, getSepAttributesPixel, -// minEdgeLength, medianEdgeLength, rectangleSize } from '../CalculateSize'; - -// describe('CalculateSize', () => { -// const mockNodeClass1: Node = { -// id: '1', -// classes: ['class1'], -// x: 0, -// y: 0 -// }; -// const mockNodeClass2: Node = { -// id: '2', -// classes: ['class2'], -// x: 0, -// y: 0 -// }; -// const mockNodeClass12: Node = { -// id: '3', -// classes: ['class1','class2'], -// x: 0, -// y: 0 -// }; -// const mockNodeClass21: Node = { -// id: '4', -// classes: ['class2','class1'], -// x: 0, -// y: 0 -// }; - -// const mockNodeClass3: Node = { -// id: '4', -// classes: ['class3'], -// x: 0, -// y: 0 -// }; - -// const mockNodeClass23: Node = { -// id: '4', -// classes: ['class2','class3'], -// x: 0, -// y: 0 -// }; - -// const mockNodeClassEmpty: Node = { -// id: '4', -// classes: [], -// x: 0, -// y: 0 -// }; - -// const mockNodeNoClass: Node = { -// id: '4', -// x: 0, -// y: 0 -// }; - - -// const mockStyle: GraphStyleProperties = { -// nodeStyles: { -// class1: { -// height: 50, -// width: 50 -// }, -// class2: { -// height: 25, -// width: 25 -// } -// } -// }; - -// const mockStyleEmpty: GraphStyleProperties = {}; - -// const mockStyleEmptyNodeStyle: GraphStyleProperties = { -// nodeStyles: {} -// }; - -// const mockNetwork: Network = { -// id: 'network', -// nodes: { -// '1': mockNodeClass1, -// '2': mockNodeClass2, -// '3': mockNodeClass12, -// '4': mockNodeClass21 -// }, -// links: [ -// { id:"link", source: mockNodeClass1, target: { ...mockNodeClass1, id: '2', x: 100, y: 100 } } -// ] -// }; - -// it('should convert pixels to inches', () => { -// expect(pixelsToInches(96)).toBe(1.33); -// }); - -// it('should convert inches to pixels', () => { -// expect(inchesToPixels(1)).toBe(72); -// }); - -// test('getSizeNodePixel', () => { -// // TEST -// const size1 = getSizeNodePixel(mockNodeClass1, mockStyle); -// const size2 = getSizeNodePixel(mockNodeClass2, mockStyle); -// const size12 = getSizeNodePixel(mockNodeClass12, mockStyle); -// const size21 = getSizeNodePixel(mockNodeClass21, mockStyle); -// const size3 = getSizeNodePixel(mockNodeClass3, mockStyle); -// const size23 = getSizeNodePixel(mockNodeClass23, mockStyle); -// const sizeEmpty = getSizeNodePixel(mockNodeClassEmpty, mockStyle); -// const sizeNoClass = getSizeNodePixel(mockNodeNoClass, mockStyle); - -// const sizeNoStyle= getSizeNodePixel(mockNodeClass1, mockStyleEmpty); -// const sizeEmptyStyle = getSizeNodePixel(mockNodeClass1, mockStyleEmptyNodeStyle); - -// // EXPECT -// expect(size).toEqual({ height: 50, width: 50 }); -// }); - -// it('should calculate the mean size of nodes in pixels', async () => { -// const size = await getMeanNodesSizePixel(Object.values(mockNetwork.nodes), mockStyle); -// expect(size).toEqual({ height: 50, width: 50 }); -// }); - -// it('should calculate the rank and node separation in inches', async () => { -// const sep = await getSepAttributesInches(mockNetwork, mockStyle); -// expect(sep).toEqual({ rankSep: 0.69, nodeSep: 0.69 }); -// }); - -// it('should calculate the rank and node separation in pixels', async () => { -// const sep = await getSepAttributesPixel(mockNetwork, mockStyle); -// expect(sep).toEqual({ rankSep: 50, nodeSep: 50 }); -// }); - -// it('should calculate the minimum edge length', () => { -// const minLength = minEdgeLength(mockNetwork); -// expect(minLength).toBe(141.42); -// }); - -// it('should calculate the median edge length', () => { -// const medianLength = medianEdgeLength(mockNetwork); -// expect(medianLength).toBe(141.42); -// }); - -// it('should calculate the size and center coordinates of a rectangle', () => { -// const coordinates: Coordinate[] = [ -// { x: 0, y: 0 }, -// { x: 100, y: 100 } -// ]; -// const size = rectangleSize(coordinates); -// expect(size).toEqual({ -// width: 100, -// height: 100, -// center: { x: 50, y: 50 } -// }); -// }); -// }); \ No newline at end of file +// Type imports +import { Node} from '@metabohub/viz-core/src/types/Node'; +import { Network } from '@metabohub/viz-core/src/types/Network'; +import { GraphStyleProperties } from '@metabohub/viz-core/src/types/GraphStyleProperties'; + +// Composable imports +import { pixelsToInches, inchesToPixels, getSizeNodePixel, + getMeanNodesSizePixel, getSepAttributesInches, getSepAttributesPixel, + minEdgeLength, medianEdgeLength, rectangleSize, + defaultHeightNode, + defaultWidthNode} from '../CalculateSize'; +import * as GetSetAttributsNodes from "../GetSetAttributsNodes"; + + + +describe('CalculateSize', () => { + const nodeClass1: Node = { + id: '1', + classes: ['class1'], + x: 0, + y: 0, + }; + const nodeClass2: Node = { + id: '2', + classes: ['class2'], + x: 0, + y: 0 + }; + const nodeClass12: Node = { + id: '3', + classes: ['class1','class2'], + x: 0, + y: 0 + }; + const nodeClass21: Node = { + id: '4', + classes: ['class2','class1'], + x: 0, + y: 0 + }; + + const nodeClass3: Node = { + id: '4', + classes: ['class3'], + x: 0, + y: 0 + }; + + const nodeClass23: Node = { + id: '4', + classes: ['class2','class3'], + x: 0, + y: 0 + }; + + const nodeClassEmpty: Node = { + id: '4', + classes: [], + x: 0, + y: 0 + }; + + const nodeNoClass: Node = { + id: '4', + x: 0, + y: 0 + }; + + + const networkStyle: GraphStyleProperties = { + nodeStyles: { + class1: { + height: 50, + width: 50 + }, + class2: { + height: 100, + width: 100 + } + } + }; + + const networkStyleEmpty: GraphStyleProperties = {}; + + const networkStyleEmptyNodeStyle: GraphStyleProperties = { + nodeStyles: {} + }; + + const networkNodes: Network = { + id: 'network', + nodes: { + '1': nodeClass1, + '2': nodeClass2, + }, + links: [ + ] + }; + + const nodesForNetwork: {[key:string]:Node} = + { + 'node1': {...nodeClass1,id:"node1"}, + 'node2': {...nodeClass1, x:50,y:100, id:"node2"}, + 'node3': {...nodeClass1, x:100,y:100,id:"node3"}, + 'node4': {...nodeClass1, x:50,y:50,id:"node4"} + }; + + const network3Edges: Network = { + id: 'network', + nodes: nodesForNetwork, + links: [ + {id:"link",source: nodesForNetwork.node1, target: nodesForNetwork.node2}, + {id:"link",source: nodesForNetwork.node2, target: nodesForNetwork.node3}, + {id:"link",source: nodesForNetwork.node3, target: nodesForNetwork.node1}, + ] + }; + + const network4Edges: Network = { + id: 'network', + nodes: nodesForNetwork, + links: [ + {id:"link",source: nodesForNetwork.node1, target: nodesForNetwork.node2}, + {id:"link",source: nodesForNetwork.node2, target: nodesForNetwork.node3}, + {id:"link",source: nodesForNetwork.node3, target: nodesForNetwork.node1}, + {id:"link",source: nodesForNetwork.node4, target: nodesForNetwork.node1}, + ] + }; + + it('should convert pixels to inches', () => { + expect(pixelsToInches(96)).toBe(1.33); + expect(pixelsToInches(432)).toBe(6); + expect(pixelsToInches(60,10)).toBe(6); + expect(pixelsToInches(10,10)).toBe(1); + }); + + it('should throw error because dpi = 0', () => { + expect(() => pixelsToInches(1,0)).toThrow(); + }); + + it('should convert inches to pixels', () => { + expect(inchesToPixels(1)).toBe(72); + expect(inchesToPixels(6)).toBe(432); + expect(inchesToPixels(1,10)).toBe(10); + expect(inchesToPixels(6,10)).toBe(60); + expect(inchesToPixels(1,0)).toBe(0); + }); + + test('getSizeNodePixel', () => { + // TEST + const size1 = getSizeNodePixel(nodeClass1, networkStyle); + const size2 = getSizeNodePixel(nodeClass2, networkStyle); + const size12 = getSizeNodePixel(nodeClass12, networkStyle); + const size21 = getSizeNodePixel(nodeClass21, networkStyle); + const size3 = getSizeNodePixel(nodeClass3, networkStyle); + const size23 = getSizeNodePixel(nodeClass23, networkStyle); + const sizeEmpty = getSizeNodePixel(nodeClassEmpty, networkStyle); + const sizeNoClass = getSizeNodePixel(nodeNoClass, networkStyle); + const sizeNoStyle= getSizeNodePixel(nodeClass1, networkStyleEmpty); + const sizeEmptyStyle = getSizeNodePixel(nodeClass1, networkStyleEmptyNodeStyle); + + // EXPECT + expect(size1).toEqual({ height: 50, width: 50 }); + expect(size2).toEqual({ height: 100, width: 100 }); + expect(size12).toEqual({ height: 100, width: 100 }); + expect(size21).toEqual({ height: 50, width: 50 }); + expect(size3).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value + expect(size23).toEqual({ height: 100, width: 100 }); + expect(sizeEmpty).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value + expect(sizeNoClass).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value + expect(sizeNoStyle).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value + expect(sizeEmptyStyle).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value + }); + + test('test mean size with all nodes', async () => { + + // DATA + const networkEmpty:Network = { + id:"network", + nodes: {}, + links: [] + }; + + // TEST + const size = await getMeanNodesSizePixel(Object.values(networkNodes.nodes), networkStyle); + const sizeEmpty = await getMeanNodesSizePixel(Object.values(networkEmpty.nodes), networkStyle); + + // EXPECT + expect(size).toEqual({ height: 75, width: 75 }); + expect(sizeEmpty).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value + }); + + + test('test mean size for non side compound nodes', async () => { + + // MOCK + const isSideCompoundMock = jest.spyOn(GetSetAttributsNodes, 'isSideCompound'); + + // Set up the mock implementation to return true for the first call and false for the second + isSideCompoundMock.mockImplementationOnce(() => true) // First call returns true + .mockImplementationOnce(() => false); // Second call returns false + + // TEST + const sizeNoSideCompound = await getMeanNodesSizePixel(Object.values(networkNodes.nodes), networkStyle,false); + + // EXPECT + expect(sizeNoSideCompound).toEqual({ height: 100, width: 100 }); + + isSideCompoundMock.mockRestore(); + }); + + test('getSepAttributesInches', async () => { + // TEST + const sep1 = await getSepAttributesInches(networkNodes, networkStyle); + const sep2 = await getSepAttributesInches(networkNodes, networkStyle,2); + + // EXPECT + expect(sep1).toEqual({ rankSep: 1.04, nodeSep: 1.04 }); + expect(sep2).toEqual({ rankSep: 2.08, nodeSep: 2.08 }); + }); + + test('getSepAttributesPixel', async () => { + // TEST + const sep1 = await getSepAttributesPixel(networkNodes, networkStyle); + const sep2 = await getSepAttributesPixel(networkNodes, networkStyle,2); + + // EXPECT + expect(sep1).toEqual({ rankSep: 75, nodeSep: 75 }); + expect(sep2).toEqual({ rankSep: 150, nodeSep: 150 }); + }); + + it('should calculate the minimum edge length when edges in cycle are included', () => { + // TEST + const minLength = minEdgeLength(network3Edges); + + // EXPECT + expect(minLength).toBe(50); + }); + + it('should calculate the minimum edge and return NaN', () => { + + // TEST + const minLength = minEdgeLength(networkNodes); + + // EXPECT + expect(minLength).toBe(NaN); + }); + + it('should calculate the minimum edge when edges in cycle', () => { + // MOCK + const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); + + // Set up the mock implementation to put node 2 and 3 in a cycle + inCycleMock.mockImplementation((network3Edges,nodeID) => { + return !(nodeID === "node1"); + }); + + // TEST + const minLength = minEdgeLength(network3Edges,false); + + // EXPECT + expect(minLength).toBe(111.8); + }); + + it('should calculate the median edge length (with odd number of edge) when edges in cycle are included', () => { + // TEST + const medianLength = medianEdgeLength(network3Edges); + + // EXPECT + expect(medianLength).toBe(111.8); + }); + + it('should calculate the median edge length (with even number of edge) when edges in cycle are included', () => { + // TEST + const medianLength = medianEdgeLength(network4Edges); + + // EXPECT + expect(medianLength).toBe(91.26); + }); + + it('should calculate the median edge length but return 0 because no links', () => { + // TEST + const medianLength = medianEdgeLength(networkNodes); + + // EXPECT + expect(medianLength).toBe(0); + }); + + it('should calculate the median edge length when edge in cycle not included', () => { + // MOCK + const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); + + // Set up the mock implementation to put node 2 and 3 in a cycle + inCycleMock.mockImplementation((network3Edges,nodeID) => { + return !(nodeID === "node1"); + }); + + // TEST + const medianLength = medianEdgeLength(network3Edges,false); + + // EXPECT + expect(medianLength).toBe(126.61); + }); + + // it('should calculate the size and center coordinates of a rectangle', () => { + // const coordinates: Coordinate[] = [ + // { x: 0, y: 0 }, + // { x: 100, y: 100 } + // ]; + // const size = rectangleSize(coordinates); + // expect(size).toEqual({ + // width: 100, + // height: 100, + // center: { x: 50, y: 50 } + // }); + // }); +}); \ No newline at end of file -- GitLab From 63996bbe567ad652332a025e6b18f4d6c3d03f3c Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Wed, 11 Sep 2024 17:30:41 +0200 Subject: [PATCH 05/13] debut test calculate size --- src/composables/CalculateSize.ts | 1 - .../__tests__/CalculateSize.tests.ts | 92 ++++++++++++++++--- .../__tests__/GetSetAttributsNodes.tests.ts | 2 + 3 files changed, 82 insertions(+), 13 deletions(-) diff --git a/src/composables/CalculateSize.ts b/src/composables/CalculateSize.ts index d4e87da..ef71104 100644 --- a/src/composables/CalculateSize.ts +++ b/src/composables/CalculateSize.ts @@ -360,7 +360,6 @@ export function getSizeAllGroupCycles(subgraphNetwork:SubgraphNetwork):SubgraphN function getSizeGroupCycles(subgraphNetwork:SubgraphNetwork,groupCycle:Subgraph):SubgraphNetwork{ if (groupCycle.precalculatedNodesPosition){ // get all nodes with x and y coordinates - console.warn('modifier getSizeGroupCycle avec nouveau attribut position'); const listNodesMetadata = Object.entries(groupCycle.precalculatedNodesPosition) .filter(([_,item]) => item.x !== undefined && item.y !== undefined); const listCoordinates = listNodesMetadata.map(([_,item]) => {return {x:item.x,y:item.y}}); diff --git a/src/composables/__tests__/CalculateSize.tests.ts b/src/composables/__tests__/CalculateSize.tests.ts index 03ab43b..4a4856b 100644 --- a/src/composables/__tests__/CalculateSize.tests.ts +++ b/src/composables/__tests__/CalculateSize.tests.ts @@ -2,6 +2,9 @@ import { Node} from '@metabohub/viz-core/src/types/Node'; import { Network } from '@metabohub/viz-core/src/types/Network'; import { GraphStyleProperties } from '@metabohub/viz-core/src/types/GraphStyleProperties'; +import { Coordinate } from '../../types/CoordinatesSize'; +import { SubgraphNetwork } from '../../types/SubgraphNetwork'; +import { NetworkLayout } from '../../types/NetworkLayout'; // Composable imports import { pixelsToInches, inchesToPixels, getSizeNodePixel, @@ -11,6 +14,9 @@ import { pixelsToInches, inchesToPixels, getSizeNodePixel, defaultWidthNode} from '../CalculateSize'; import * as GetSetAttributsNodes from "../GetSetAttributsNodes"; +// General imports +import { ref } from 'vue'; // Import the 'ref' function from the 'vue' module + describe('CalculateSize', () => { @@ -125,6 +131,8 @@ describe('CalculateSize', () => { ] }; + // 0. Nodes + it('should convert pixels to inches', () => { expect(pixelsToInches(96)).toBe(1.33); expect(pixelsToInches(432)).toBe(6); @@ -227,6 +235,8 @@ describe('CalculateSize', () => { expect(sep2).toEqual({ rankSep: 150, nodeSep: 150 }); }); +// 1. Edges + it('should calculate the minimum edge length when edges in cycle are included', () => { // TEST const minLength = minEdgeLength(network3Edges); @@ -300,16 +310,74 @@ describe('CalculateSize', () => { expect(medianLength).toBe(126.61); }); - // it('should calculate the size and center coordinates of a rectangle', () => { - // const coordinates: Coordinate[] = [ - // { x: 0, y: 0 }, - // { x: 100, y: 100 } - // ]; - // const size = rectangleSize(coordinates); - // expect(size).toEqual({ - // width: 100, - // height: 100, - // center: { x: 50, y: 50 } - // }); - // }); + // 2. Subgraphs + + it('should calculate the size and center coordinates of a rectangle, when no style', () => { + + // DATA + const coordinates: Coordinate[] = [ + { x: 0, y: 0 }, + { x: 100, y: 100 } + ]; + // TEST + const size = rectangleSize(coordinates); + + // EXPECT + expect(size).toEqual({ + width: 100, + height: 100, + center: { x: 50, y: 50 } + }); + }); + + it('should calculate the size and center coordinates of a rectangle, when style', () => { + + // DATA + const subgraphNetwork:SubgraphNetwork = { + network: ref<NetworkLayout>(network3Edges), + networkStyle: ref<GraphStyleProperties>(networkStyle) + }; + const listID = ["node1","node2"] + const coordinates: Coordinate[] = [ + { x: 0, y: 0 }, + { x: 100, y: 100 } + ]; + // TEST + const size = rectangleSize(coordinates,listID,subgraphNetwork); + + // EXPECT + expect(size).toEqual({ + width: 150, + height: 150, + center: { x: 50, y: 50 } + }); + }); + + test('getSizeAllGroupCycles', () => { + expect(true).toBe(false); + }); + + test('getSizeGroupCycles', () => { + expect(true).toBe(false); + }); + + +// 3. Shift coordinates depending on size + + + test('shiftAllToGetTopLeftCoord', () => { + expect(true).toBe(false); + }); + + test('getTopLeftCoordFromCenter', () => { + expect(true).toBe(false); + }); + + + test('getCenterCoordFromTopLeft', () => { + expect(true).toBe(false); + }); + + + }); \ No newline at end of file diff --git a/src/composables/__tests__/GetSetAttributsNodes.tests.ts b/src/composables/__tests__/GetSetAttributsNodes.tests.ts index 34ccdd2..7e249fa 100644 --- a/src/composables/__tests__/GetSetAttributsNodes.tests.ts +++ b/src/composables/__tests__/GetSetAttributsNodes.tests.ts @@ -292,6 +292,7 @@ describe('GetSetAttributsNodes', () => { test("addMetadataReversibleWithClass", async () => { + const addReversibleNetworkMock = jest.spyOn(GetSetAttributsNodes, 'addReversibleNetwork'); // DATA let network:Network = { @@ -360,6 +361,7 @@ describe('GetSetAttributsNodes', () => { await GetSetAttributsNodes.addMetadataReversibleWithClass(network); // EXPECT + //expect(addReversibleNetworkMock).toHaveBeenCalled(); expect(network).toEqual(networkExpected); }); -- GitLab From 66144dae9cd27ced9aa65c6705cc12b124d72c58 Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Thu, 12 Sep 2024 08:40:00 +0200 Subject: [PATCH 06/13] test mock intern but doesnt work --- .../__tests__/CalculateSize.tests.ts | 204 ++++++++++++------ 1 file changed, 140 insertions(+), 64 deletions(-) diff --git a/src/composables/__tests__/CalculateSize.tests.ts b/src/composables/__tests__/CalculateSize.tests.ts index 4a4856b..c429293 100644 --- a/src/composables/__tests__/CalculateSize.tests.ts +++ b/src/composables/__tests__/CalculateSize.tests.ts @@ -4,15 +4,13 @@ import { Network } from '@metabohub/viz-core/src/types/Network'; import { GraphStyleProperties } from '@metabohub/viz-core/src/types/GraphStyleProperties'; import { Coordinate } from '../../types/CoordinatesSize'; import { SubgraphNetwork } from '../../types/SubgraphNetwork'; -import { NetworkLayout } from '../../types/NetworkLayout'; +import { NetworkLayout, NodeLayout } from '../../types/NetworkLayout'; +import { Subgraph, TypeSubgraph } from '../../types/Subgraph'; + // Composable imports -import { pixelsToInches, inchesToPixels, getSizeNodePixel, - getMeanNodesSizePixel, getSepAttributesInches, getSepAttributesPixel, - minEdgeLength, medianEdgeLength, rectangleSize, - defaultHeightNode, - defaultWidthNode} from '../CalculateSize'; import * as GetSetAttributsNodes from "../GetSetAttributsNodes"; +import * as CalculateSize from "../CalculateSize"; // General imports import { ref } from 'vue'; // Import the 'ref' function from the 'vue' module @@ -20,53 +18,53 @@ import { ref } from 'vue'; // Import the 'ref' function from the 'vue' module describe('CalculateSize', () => { - const nodeClass1: Node = { + const nodeClass1: NodeLayout = { id: '1', classes: ['class1'], x: 0, y: 0, }; - const nodeClass2: Node = { + const nodeClass2: NodeLayout = { id: '2', classes: ['class2'], x: 0, - y: 0 + y: 0, }; - const nodeClass12: Node = { + const nodeClass12: NodeLayout = { id: '3', classes: ['class1','class2'], x: 0, y: 0 }; - const nodeClass21: Node = { + const nodeClass21: NodeLayout = { id: '4', classes: ['class2','class1'], x: 0, y: 0 }; - const nodeClass3: Node = { + const nodeClass3: NodeLayout = { id: '4', classes: ['class3'], x: 0, y: 0 }; - const nodeClass23: Node = { + const nodeClass23: NodeLayout = { id: '4', classes: ['class2','class3'], x: 0, y: 0 }; - const nodeClassEmpty: Node = { + const nodeClassEmpty: NodeLayout = { id: '4', classes: [], x: 0, y: 0 }; - const nodeNoClass: Node = { + const nodeNoClass: NodeLayout = { id: '4', x: 0, y: 0 @@ -92,7 +90,7 @@ describe('CalculateSize', () => { nodeStyles: {} }; - const networkNodes: Network = { + const networkNodes: NetworkLayout = { id: 'network', nodes: { '1': nodeClass1, @@ -102,7 +100,7 @@ describe('CalculateSize', () => { ] }; - const nodesForNetwork: {[key:string]:Node} = + const nodesForNetwork: {[key:string]:NodeLayout} = { 'node1': {...nodeClass1,id:"node1"}, 'node2': {...nodeClass1, x:50,y:100, id:"node2"}, @@ -110,7 +108,7 @@ describe('CalculateSize', () => { 'node4': {...nodeClass1, x:50,y:50,id:"node4"} }; - const network3Edges: Network = { + const network3Edges: NetworkLayout = { id: 'network', nodes: nodesForNetwork, links: [ @@ -120,7 +118,7 @@ describe('CalculateSize', () => { ] }; - const network4Edges: Network = { + const network4Edges: NetworkLayout = { id: 'network', nodes: nodesForNetwork, links: [ @@ -134,48 +132,48 @@ describe('CalculateSize', () => { // 0. Nodes it('should convert pixels to inches', () => { - expect(pixelsToInches(96)).toBe(1.33); - expect(pixelsToInches(432)).toBe(6); - expect(pixelsToInches(60,10)).toBe(6); - expect(pixelsToInches(10,10)).toBe(1); + expect(CalculateSize.pixelsToInches(96)).toBe(1.33); + expect(CalculateSize.pixelsToInches(432)).toBe(6); + expect(CalculateSize.pixelsToInches(60,10)).toBe(6); + expect(CalculateSize.pixelsToInches(10,10)).toBe(1); }); it('should throw error because dpi = 0', () => { - expect(() => pixelsToInches(1,0)).toThrow(); + expect(() => CalculateSize.pixelsToInches(1,0)).toThrow(); }); it('should convert inches to pixels', () => { - expect(inchesToPixels(1)).toBe(72); - expect(inchesToPixels(6)).toBe(432); - expect(inchesToPixels(1,10)).toBe(10); - expect(inchesToPixels(6,10)).toBe(60); - expect(inchesToPixels(1,0)).toBe(0); + expect(CalculateSize.inchesToPixels(1)).toBe(72); + expect(CalculateSize.inchesToPixels(6)).toBe(432); + expect(CalculateSize.inchesToPixels(1,10)).toBe(10); + expect(CalculateSize.inchesToPixels(6,10)).toBe(60); + expect(CalculateSize.inchesToPixels(1,0)).toBe(0); }); test('getSizeNodePixel', () => { // TEST - const size1 = getSizeNodePixel(nodeClass1, networkStyle); - const size2 = getSizeNodePixel(nodeClass2, networkStyle); - const size12 = getSizeNodePixel(nodeClass12, networkStyle); - const size21 = getSizeNodePixel(nodeClass21, networkStyle); - const size3 = getSizeNodePixel(nodeClass3, networkStyle); - const size23 = getSizeNodePixel(nodeClass23, networkStyle); - const sizeEmpty = getSizeNodePixel(nodeClassEmpty, networkStyle); - const sizeNoClass = getSizeNodePixel(nodeNoClass, networkStyle); - const sizeNoStyle= getSizeNodePixel(nodeClass1, networkStyleEmpty); - const sizeEmptyStyle = getSizeNodePixel(nodeClass1, networkStyleEmptyNodeStyle); + const size1 = CalculateSize.getSizeNodePixel(nodeClass1, networkStyle); + const size2 = CalculateSize.getSizeNodePixel(nodeClass2, networkStyle); + const size12 = CalculateSize.getSizeNodePixel(nodeClass12, networkStyle); + const size21 = CalculateSize.getSizeNodePixel(nodeClass21, networkStyle); + const size3 = CalculateSize.getSizeNodePixel(nodeClass3, networkStyle); + const size23 = CalculateSize.getSizeNodePixel(nodeClass23, networkStyle); + const sizeEmpty = CalculateSize.getSizeNodePixel(nodeClassEmpty, networkStyle); + const sizeNoClass = CalculateSize.getSizeNodePixel(nodeNoClass, networkStyle); + const sizeNoStyle= CalculateSize.getSizeNodePixel(nodeClass1, networkStyleEmpty); + const sizeEmptyStyle = CalculateSize.getSizeNodePixel(nodeClass1, networkStyleEmptyNodeStyle); // EXPECT expect(size1).toEqual({ height: 50, width: 50 }); expect(size2).toEqual({ height: 100, width: 100 }); expect(size12).toEqual({ height: 100, width: 100 }); expect(size21).toEqual({ height: 50, width: 50 }); - expect(size3).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value + expect(size3).toEqual({ height: CalculateSize.defaultHeightNode, width: CalculateSize.defaultWidthNode }); // default value expect(size23).toEqual({ height: 100, width: 100 }); - expect(sizeEmpty).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value - expect(sizeNoClass).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value - expect(sizeNoStyle).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value - expect(sizeEmptyStyle).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value + expect(sizeEmpty).toEqual({ height: CalculateSize.defaultHeightNode, width: CalculateSize.defaultWidthNode }); // default value + expect(sizeNoClass).toEqual({ height: CalculateSize.defaultHeightNode, width: CalculateSize.defaultWidthNode }); // default value + expect(sizeNoStyle).toEqual({ height: CalculateSize.defaultHeightNode, width: CalculateSize.defaultWidthNode }); // default value + expect(sizeEmptyStyle).toEqual({ height: CalculateSize.defaultHeightNode, width: CalculateSize.defaultWidthNode }); // default value }); test('test mean size with all nodes', async () => { @@ -188,12 +186,12 @@ describe('CalculateSize', () => { }; // TEST - const size = await getMeanNodesSizePixel(Object.values(networkNodes.nodes), networkStyle); - const sizeEmpty = await getMeanNodesSizePixel(Object.values(networkEmpty.nodes), networkStyle); + const size = await CalculateSize.getMeanNodesSizePixel(Object.values(networkNodes.nodes), networkStyle); + const sizeEmpty = await CalculateSize.getMeanNodesSizePixel(Object.values(networkEmpty.nodes), networkStyle); // EXPECT expect(size).toEqual({ height: 75, width: 75 }); - expect(sizeEmpty).toEqual({ height: defaultHeightNode, width: defaultWidthNode }); // default value + expect(sizeEmpty).toEqual({ height: CalculateSize.defaultHeightNode, width: CalculateSize.defaultWidthNode }); // default value }); @@ -207,7 +205,7 @@ describe('CalculateSize', () => { .mockImplementationOnce(() => false); // Second call returns false // TEST - const sizeNoSideCompound = await getMeanNodesSizePixel(Object.values(networkNodes.nodes), networkStyle,false); + const sizeNoSideCompound = await CalculateSize.getMeanNodesSizePixel(Object.values(networkNodes.nodes), networkStyle,false); // EXPECT expect(sizeNoSideCompound).toEqual({ height: 100, width: 100 }); @@ -217,8 +215,8 @@ describe('CalculateSize', () => { test('getSepAttributesInches', async () => { // TEST - const sep1 = await getSepAttributesInches(networkNodes, networkStyle); - const sep2 = await getSepAttributesInches(networkNodes, networkStyle,2); + const sep1 = await CalculateSize.getSepAttributesInches(networkNodes, networkStyle); + const sep2 = await CalculateSize.getSepAttributesInches(networkNodes, networkStyle,2); // EXPECT expect(sep1).toEqual({ rankSep: 1.04, nodeSep: 1.04 }); @@ -227,8 +225,8 @@ describe('CalculateSize', () => { test('getSepAttributesPixel', async () => { // TEST - const sep1 = await getSepAttributesPixel(networkNodes, networkStyle); - const sep2 = await getSepAttributesPixel(networkNodes, networkStyle,2); + const sep1 = await CalculateSize.getSepAttributesPixel(networkNodes, networkStyle); + const sep2 = await CalculateSize.getSepAttributesPixel(networkNodes, networkStyle,2); // EXPECT expect(sep1).toEqual({ rankSep: 75, nodeSep: 75 }); @@ -239,7 +237,7 @@ describe('CalculateSize', () => { it('should calculate the minimum edge length when edges in cycle are included', () => { // TEST - const minLength = minEdgeLength(network3Edges); + const minLength = CalculateSize.minEdgeLength(network3Edges); // EXPECT expect(minLength).toBe(50); @@ -248,7 +246,7 @@ describe('CalculateSize', () => { it('should calculate the minimum edge and return NaN', () => { // TEST - const minLength = minEdgeLength(networkNodes); + const minLength = CalculateSize.minEdgeLength(networkNodes); // EXPECT expect(minLength).toBe(NaN); @@ -264,7 +262,7 @@ describe('CalculateSize', () => { }); // TEST - const minLength = minEdgeLength(network3Edges,false); + const minLength = CalculateSize.minEdgeLength(network3Edges,false); // EXPECT expect(minLength).toBe(111.8); @@ -272,7 +270,7 @@ describe('CalculateSize', () => { it('should calculate the median edge length (with odd number of edge) when edges in cycle are included', () => { // TEST - const medianLength = medianEdgeLength(network3Edges); + const medianLength = CalculateSize.medianEdgeLength(network3Edges); // EXPECT expect(medianLength).toBe(111.8); @@ -280,7 +278,7 @@ describe('CalculateSize', () => { it('should calculate the median edge length (with even number of edge) when edges in cycle are included', () => { // TEST - const medianLength = medianEdgeLength(network4Edges); + const medianLength = CalculateSize.medianEdgeLength(network4Edges); // EXPECT expect(medianLength).toBe(91.26); @@ -288,7 +286,7 @@ describe('CalculateSize', () => { it('should calculate the median edge length but return 0 because no links', () => { // TEST - const medianLength = medianEdgeLength(networkNodes); + const medianLength = CalculateSize.medianEdgeLength(networkNodes); // EXPECT expect(medianLength).toBe(0); @@ -304,7 +302,7 @@ describe('CalculateSize', () => { }); // TEST - const medianLength = medianEdgeLength(network3Edges,false); + const medianLength = CalculateSize.medianEdgeLength(network3Edges,false); // EXPECT expect(medianLength).toBe(126.61); @@ -320,7 +318,7 @@ describe('CalculateSize', () => { { x: 100, y: 100 } ]; // TEST - const size = rectangleSize(coordinates); + const size = CalculateSize.rectangleSize(coordinates); // EXPECT expect(size).toEqual({ @@ -343,7 +341,7 @@ describe('CalculateSize', () => { { x: 100, y: 100 } ]; // TEST - const size = rectangleSize(coordinates,listID,subgraphNetwork); + const size = CalculateSize.rectangleSize(coordinates,listID,subgraphNetwork); // EXPECT expect(size).toEqual({ @@ -353,12 +351,90 @@ describe('CalculateSize', () => { }); }); - test('getSizeAllGroupCycles', () => { + test('getSizeGroupCycles', () => { expect(true).toBe(false); }); - test('getSizeGroupCycles', () => { - expect(true).toBe(false); + it('should put the size and center of the rectangle for all cycle group subgraph in the subgraph', () => { + + // DATA + let subgraph0:Subgraph = { + name: "groupCycle_0", + nodes:["node1","node2"], + precalculatedNodesPosition:{ + node1: { x: 0, y: 0 }, + node2:{ x: 100, y: 100 } + } + }; + let subgraph1:Subgraph = { + name: "groupCycle_1", + nodes:["node3","node4"], + precalculatedNodesPosition:{ + node3: { x: 10, y: 50 }, + node4:{ x: 73, y: 25 } + } + }; + let subgraphNetwork:SubgraphNetwork = { + network: ref<NetworkLayout>(network3Edges), + networkStyle: ref<GraphStyleProperties>(networkStyle), + [TypeSubgraph.CYCLEGROUP]:{ + "groupCycle_0": subgraph0, + "groupCycle_1":subgraph1 + } + }; + + const subgraphNetworkExpected:SubgraphNetwork = { + network: ref<NetworkLayout>(network3Edges), + networkStyle: ref<GraphStyleProperties>(networkStyle), + [TypeSubgraph.CYCLEGROUP]:{ + "groupCycle_0": { + ...subgraph0, + width : -1, + height :-1, + originalPosition: { x: 0, y: 0 } + }, + "groupCycle_1": { + ...subgraph1, + width : -1, + height :-1, + originalPosition: { x: 0, y: 0 } + } + } + }; + + const listIDExpected1 = ["node3","node4"] + const coordinatesExpected1: Coordinate[] = [ + { x: 10, y: 50 }, + { x: 73, y: 25 } + ]; + + const listIDExpected2 = ["node1","node2"] + const coordinatesExpected2: Coordinate[] = [ + { x: 0, y: 0 }, + { x: 100, y: 100 } + ]; + + // MOCK + const rectangleSizeMock = jest.spyOn(CalculateSize, 'rectangleSize'); + + // Set up the mock implementation + rectangleSizeMock.mockImplementation((coordinates,listID,subgraphNetwork) => { + return { + width: -1, + height: -1, + center: { x: 0, y: 0 } + }; + }); + + + // TEST + subgraphNetwork=CalculateSize.getSizeAllGroupCycles(subgraphNetwork); + expect(rectangleSizeMock).toHaveBeenCalledTimes(2); + expect(rectangleSizeMock).toHaveBeenCalledWith(coordinatesExpected1, listIDExpected1, subgraphNetwork); + expect(rectangleSizeMock).toHaveBeenCalledWith(coordinatesExpected2, listIDExpected2, subgraphNetwork); + + expect(subgraphNetwork).toEqual(subgraphNetworkExpected); + }); -- GitLab From dd6c66af4beab55c3f51dfabf3d99bc29c421e5a Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Thu, 12 Sep 2024 09:21:47 +0200 Subject: [PATCH 07/13] test calculate size --- src/composables/CalculateSize.ts | 4 + .../__tests__/CalculateSize.tests.ts | 229 +++++++++++------- 2 files changed, 140 insertions(+), 93 deletions(-) diff --git a/src/composables/CalculateSize.ts b/src/composables/CalculateSize.ts index ef71104..b1366aa 100644 --- a/src/composables/CalculateSize.ts +++ b/src/composables/CalculateSize.ts @@ -290,6 +290,7 @@ export function rectangleSize(listCoordinates:Coordinate[],listID?:string[],subg minX = -defaultWidthNode/2; } else { const minXNode = listID[minXIndex]; + if (!(minXNode in subgraphNetwork.network.value.nodes)) throw new Error("Node not in network"); const sizeMinXNode = getSizeNodePixel(subgraphNetwork.network.value.nodes[minXNode],subgraphNetwork.networkStyle.value); minX = minX - sizeMinXNode.width/2; } @@ -299,6 +300,7 @@ export function rectangleSize(listCoordinates:Coordinate[],listID?:string[],subg maxX = defaultWidthNode/2; }else { const maxXNode = listID[maxXIndex]; + if (!(maxXNode in subgraphNetwork.network.value.nodes)) throw new Error("Node not in network"); const sizeMaxXNode = getSizeNodePixel(subgraphNetwork.network.value.nodes[maxXNode],subgraphNetwork.networkStyle.value); maxX = maxX + sizeMaxXNode.width/2; } @@ -308,6 +310,7 @@ export function rectangleSize(listCoordinates:Coordinate[],listID?:string[],subg minY = -defaultHeightNode/2; } else { const minYNode = listID[minYIndex]; + if (!(minYNode in subgraphNetwork.network.value.nodes)) throw new Error("Node not in network"); const sizeMinYNode = getSizeNodePixel(subgraphNetwork.network.value.nodes[minYNode],subgraphNetwork.networkStyle.value); minY = minY - sizeMinYNode.height/2; } @@ -317,6 +320,7 @@ export function rectangleSize(listCoordinates:Coordinate[],listID?:string[],subg maxY = defaultHeightNode/2; } else { const maxYNode = listID[maxYIndex]; + if (!(maxYNode in subgraphNetwork.network.value.nodes)) throw new Error("Node not in network"); const sizeMaxYNode = getSizeNodePixel(subgraphNetwork.network.value.nodes[maxYNode],subgraphNetwork.networkStyle.value); maxY = maxY + sizeMaxYNode.height/2; } diff --git a/src/composables/__tests__/CalculateSize.tests.ts b/src/composables/__tests__/CalculateSize.tests.ts index c429293..f344418 100644 --- a/src/composables/__tests__/CalculateSize.tests.ts +++ b/src/composables/__tests__/CalculateSize.tests.ts @@ -18,18 +18,64 @@ import { ref } from 'vue'; // Import the 'ref' function from the 'vue' module describe('CalculateSize', () => { - const nodeClass1: NodeLayout = { - id: '1', - classes: ['class1'], - x: 0, - y: 0, - }; - const nodeClass2: NodeLayout = { - id: '2', - classes: ['class2'], - x: 0, - y: 0, - }; + let nodeClass1: NodeLayout; + let nodeClass2: NodeLayout; + let networkNodes: NetworkLayout; + let nodesForNetwork: {[key: string]: NodeLayout}; + let network3Edges: NetworkLayout; + let network4Edges: NetworkLayout; + + beforeEach(() => { + nodeClass1 = { + id: '1', + classes: ['class1'], + x: 0, + y: 0, + }; + nodeClass2 = { + id: '2', + classes: ['class2'], + x: 0, + y: 0, + }; + networkNodes = { + id: 'network', + nodes: { + '1': nodeClass1, + '2': nodeClass2, + }, + links: [ + ] + }; + nodesForNetwork = + { + 'node1': {...nodeClass1,id:"node1"}, + 'node2': {...nodeClass1, x:50,y:100, id:"node2"}, + 'node3': {...nodeClass1, x:100,y:100,id:"node3"}, + 'node4': {...nodeClass1, x:50,y:50,id:"node4"} + }; + + network3Edges = { + id: 'network', + nodes: nodesForNetwork, + links: [ + {id:"link",source: nodesForNetwork.node1, target: nodesForNetwork.node2}, + {id:"link",source: nodesForNetwork.node2, target: nodesForNetwork.node3}, + {id:"link",source: nodesForNetwork.node3, target: nodesForNetwork.node1}, + ] + }; + + network4Edges = { + id: 'network', + nodes: nodesForNetwork, + links: [ + {id:"link",source: nodesForNetwork.node1, target: nodesForNetwork.node2}, + {id:"link",source: nodesForNetwork.node2, target: nodesForNetwork.node3}, + {id:"link",source: nodesForNetwork.node3, target: nodesForNetwork.node1}, + {id:"link",source: nodesForNetwork.node4, target: nodesForNetwork.node1}, + ] + }; + }); const nodeClass12: NodeLayout = { id: '3', classes: ['class1','class2'], @@ -90,44 +136,7 @@ describe('CalculateSize', () => { nodeStyles: {} }; - const networkNodes: NetworkLayout = { - id: 'network', - nodes: { - '1': nodeClass1, - '2': nodeClass2, - }, - links: [ - ] - }; - - const nodesForNetwork: {[key:string]:NodeLayout} = - { - 'node1': {...nodeClass1,id:"node1"}, - 'node2': {...nodeClass1, x:50,y:100, id:"node2"}, - 'node3': {...nodeClass1, x:100,y:100,id:"node3"}, - 'node4': {...nodeClass1, x:50,y:50,id:"node4"} - }; - - const network3Edges: NetworkLayout = { - id: 'network', - nodes: nodesForNetwork, - links: [ - {id:"link",source: nodesForNetwork.node1, target: nodesForNetwork.node2}, - {id:"link",source: nodesForNetwork.node2, target: nodesForNetwork.node3}, - {id:"link",source: nodesForNetwork.node3, target: nodesForNetwork.node1}, - ] - }; - - const network4Edges: NetworkLayout = { - id: 'network', - nodes: nodesForNetwork, - links: [ - {id:"link",source: nodesForNetwork.node1, target: nodesForNetwork.node2}, - {id:"link",source: nodesForNetwork.node2, target: nodesForNetwork.node3}, - {id:"link",source: nodesForNetwork.node3, target: nodesForNetwork.node1}, - {id:"link",source: nodesForNetwork.node4, target: nodesForNetwork.node1}, - ] - }; + // 0. Nodes @@ -351,8 +360,23 @@ describe('CalculateSize', () => { }); }); - test('getSizeGroupCycles', () => { - expect(true).toBe(false); + it('should throw an error when calculate the size a rectangle because node not in network', () => { + + // DATA + const subgraphNetwork:SubgraphNetwork = { + network: ref<NetworkLayout>(network3Edges), + networkStyle: ref<GraphStyleProperties>(networkStyle) + }; + const listID = ["node1","nodeNotNetwork"] + const coordinates: Coordinate[] = [ + { x: 0, y: 0 }, + { x: 100, y: 100 } + ]; + + // EXPECT + expect( + ()=> CalculateSize.rectangleSize(coordinates,listID,subgraphNetwork) + ).toThrow(); }); it('should put the size and center of the rectangle for all cycle group subgraph in the subgraph', () => { @@ -360,7 +384,7 @@ describe('CalculateSize', () => { // DATA let subgraph0:Subgraph = { name: "groupCycle_0", - nodes:["node1","node2"], + nodes:[], precalculatedNodesPosition:{ node1: { x: 0, y: 0 }, node2:{ x: 100, y: 100 } @@ -368,7 +392,7 @@ describe('CalculateSize', () => { }; let subgraph1:Subgraph = { name: "groupCycle_1", - nodes:["node3","node4"], + nodes:[], precalculatedNodesPosition:{ node3: { x: 10, y: 50 }, node4:{ x: 73, y: 25 } @@ -389,50 +413,23 @@ describe('CalculateSize', () => { [TypeSubgraph.CYCLEGROUP]:{ "groupCycle_0": { ...subgraph0, - width : -1, - height :-1, - originalPosition: { x: 0, y: 0 } + width : 150, + height :150, + originalPosition: { x: 50, y: 50 } }, "groupCycle_1": { ...subgraph1, - width : -1, - height :-1, - originalPosition: { x: 0, y: 0 } + width : 113, + height :75, + originalPosition: { x: 41.5, y: 37.5 } } } }; - const listIDExpected1 = ["node3","node4"] - const coordinatesExpected1: Coordinate[] = [ - { x: 10, y: 50 }, - { x: 73, y: 25 } - ]; - - const listIDExpected2 = ["node1","node2"] - const coordinatesExpected2: Coordinate[] = [ - { x: 0, y: 0 }, - { x: 100, y: 100 } - ]; - - // MOCK - const rectangleSizeMock = jest.spyOn(CalculateSize, 'rectangleSize'); - - // Set up the mock implementation - rectangleSizeMock.mockImplementation((coordinates,listID,subgraphNetwork) => { - return { - width: -1, - height: -1, - center: { x: 0, y: 0 } - }; - }); // TEST subgraphNetwork=CalculateSize.getSizeAllGroupCycles(subgraphNetwork); - expect(rectangleSizeMock).toHaveBeenCalledTimes(2); - expect(rectangleSizeMock).toHaveBeenCalledWith(coordinatesExpected1, listIDExpected1, subgraphNetwork); - expect(rectangleSizeMock).toHaveBeenCalledWith(coordinatesExpected2, listIDExpected2, subgraphNetwork); - expect(subgraphNetwork).toEqual(subgraphNetworkExpected); }); @@ -440,20 +437,66 @@ describe('CalculateSize', () => { // 3. Shift coordinates depending on size + it('should return coordinate of top left corner instead of center', () => { + // TEST + const coord= CalculateSize.getTopLeftCoordFromCenter(nodeClass1, networkStyle); - test('shiftAllToGetTopLeftCoord', () => { - expect(true).toBe(false); + // EXPECT + expect(coord).toEqual({ x: -25, y: -25 }); }); - test('getTopLeftCoordFromCenter', () => { - expect(true).toBe(false); + + it('should return coordinate of center instead of top left corner', () => { + // TEST + const coord= CalculateSize.getCenterCoordFromTopLeft(nodeClass1, networkStyle); + + // EXPECT + expect(coord).toEqual({ x: 25, y: 25 }); }); + it('should shift all coordinate to left top corner', () => { + // DATA + + const networkShiftedExpected: NetworkLayout = { + id: 'network', + nodes: { + '1': { ...nodeClass1, x: -25, y: -25 }, + '2': { ...nodeClass2, x: -50, y: -50 }, + }, + links: [ + ] + }; + // TEST + CalculateSize.shiftAllToGetTopLeftCoord(networkNodes, networkStyle); - test('getCenterCoordFromTopLeft', () => { - expect(true).toBe(false); + // EXPECT + expect(networkNodes).toEqual(networkShiftedExpected); }); + it('should not shift all coordinate to left top corner (not the cycle)', () => { + // MOCK + const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); + inCycleMock.mockImplementation((network,nodeID) => { + return nodeID === "1"; + }); + + + // DATA + + const networkShiftedExpected: NetworkLayout = { + id: 'network', + nodes: { + '1': { ...nodeClass1, x: 0, y: 0 }, + '2': { ...nodeClass2, x: -50, y: -50 }, + }, + links: [ + ] + }; + // TEST + CalculateSize.shiftAllToGetTopLeftCoord(networkNodes, networkStyle,false); + // EXPECT + expect(networkNodes).toEqual(networkShiftedExpected); + }); }); \ No newline at end of file -- GitLab From a4e27995b57bd282046079d04d472c90c3efd8d2 Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Thu, 12 Sep 2024 11:58:26 +0200 Subject: [PATCH 08/13] re clean de fichier avec nouveau linter --- src/composables/CalculateRelationCycle.ts | 116 ++++---- src/composables/CalculateStartNodes.ts | 59 ++-- src/composables/ConvertFromNetwork.ts | 269 +++++++++--------- src/composables/ConvertToNetwork.ts | 102 ++++--- src/composables/LayoutManageSideCompounds.ts | 52 ++-- src/composables/LayoutReversibleReactions.ts | 13 +- .../__tests__/CalculateStartNodes.tests.ts | 102 +++++++ 7 files changed, 410 insertions(+), 303 deletions(-) create mode 100644 src/composables/__tests__/CalculateStartNodes.tests.ts diff --git a/src/composables/CalculateRelationCycle.ts b/src/composables/CalculateRelationCycle.ts index 2266b2b..ccde993 100644 --- a/src/composables/CalculateRelationCycle.ts +++ b/src/composables/CalculateRelationCycle.ts @@ -8,6 +8,7 @@ import { LinkLayout, NetworkLayout } from "../types/NetworkLayout"; // Composable imports import { inCycle } from "./GetSetAttributsNodes"; +import { Coordinate } from "../types/CoordinatesSize"; /** @@ -90,28 +91,35 @@ import { inCycle } from "./GetSetAttributsNodes"; * @returns An array of cycle IDs representing the neighboring cycles. */ export function neighborsGroupCycle(subgraphNetwork:SubgraphNetwork,cycleGroupId:string, parentOrChild:"parent"|"child",xSort:boolean=true):string[]{ - if (cycleGroupId in subgraphNetwork[TypeSubgraph.CYCLEGROUP] && subgraphNetwork[TypeSubgraph.CYCLEGROUP][cycleGroupId].precalculatedNodesPosition){ - // get the id of nodes in group cycle - const nodes=getNodesIDPlacedInGroupCycle(subgraphNetwork,cycleGroupId); - // sort nodes of the group cycle by x - if (xSort){ - nodes.sort((nodeIdA, nodeIdB) => { - const nodeA = subgraphNetwork[TypeSubgraph.CYCLEGROUP][cycleGroupId].precalculatedNodesPosition[nodeIdA]; - const nodeB = subgraphNetwork[TypeSubgraph.CYCLEGROUP][cycleGroupId].precalculatedNodesPosition[nodeIdB]; - return nodeA.x - nodeB.x; - }); - } - if (parentOrChild==="parent"){ - // get parent nodes - const parentCycles = Array.from(new Set(parentNodeNotInCycle(subgraphNetwork, nodes).flat())); - return parentCycles; - } else { - // get child nodes - const childCycles = Array.from(new Set(childNodeNotInCycle(subgraphNetwork, nodes).flat())); - return childCycles; + 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); + // sort nodes of the group cycle by x + if (xSort){ + nodes.sort((nodeIdA, nodeIdB) => { + if(!(nodeIdA in positionNodesCycleGroup) || !(nodeIdB in positionNodesCycleGroup)) throw new Error("Node not plaed inside groupe cycle"); + const nodeA = positionNodesCycleGroup[nodeIdA]; + const nodeB = positionNodesCycleGroup[nodeIdB]; + return nodeA.x - nodeB.x; + }); + } + if (parentOrChild==="parent"){ + // get parent nodes + const parentCycles = Array.from(new Set(parentNodeNotInCycle(subgraphNetwork, nodes).flat())); + return parentCycles; + } else { + // get child nodes + const childCycles = Array.from(new Set(childNodeNotInCycle(subgraphNetwork, nodes).flat())); + return childCycles; + } + }else{ + return []; // no node placed inside cyclegroup } }else{ - return []; + throw new Error("Group cycle not in subgraphNetwork"); } } @@ -165,12 +173,18 @@ export function childNodeNotInCycle(subgraphNetwork: SubgraphNetwork, listNodes: * @returns An array of strings representing the IDs of the nodes placed in the group cycle. */ export function getNodesIDPlacedInGroupCycle(subgraphNetwork:SubgraphNetwork,groupCycleID:string):string[]{ - if (groupCycleID in subgraphNetwork[TypeSubgraph.CYCLEGROUP] && subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleID].precalculatedNodesPosition){ - return Object.entries(subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleID].precalculatedNodesPosition) + if (subgraphNetwork[TypeSubgraph.CYCLEGROUP] && groupCycleID in subgraphNetwork[TypeSubgraph.CYCLEGROUP]){ + const groupCycle =subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleID]; + if (groupCycle.precalculatedNodesPosition){ + const positionNodesCycleGroup=groupCycle.precalculatedNodesPosition; + return Object.entries(positionNodesCycleGroup) .filter(([_,item]) => item.x !== undefined && item.y !== undefined) .map(([key,_])=>key); + }else{ + return []; + } }else{ - return []; + throw new Error("Cycle group not in subgraphNetwork"); } } @@ -185,8 +199,11 @@ export function getNodesIDPlacedInGroupCycle(subgraphNetwork:SubgraphNetwork,gro * If the group cycle ID is not found or the precalculated node positions are not available, null is returned. */ export function getNodesPlacedInGroupCycle(subgraphNetwork:SubgraphNetwork,groupCycleID:string,positionAsFixed:boolean=false):{ id: string,x?:number, y?:number, fx?:number, fy?:number }[]{ - if (groupCycleID in subgraphNetwork[TypeSubgraph.CYCLEGROUP] && subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleID].precalculatedNodesPosition){ - return Object.entries(subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleID].precalculatedNodesPosition) + if (subgraphNetwork[TypeSubgraph.CYCLEGROUP] && groupCycleID in subgraphNetwork[TypeSubgraph.CYCLEGROUP]){ + const groupCycle =subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleID]; + if (groupCycle.precalculatedNodesPosition){ + const positionNodesCycleGroup=groupCycle.precalculatedNodesPosition; + return Object.entries(positionNodesCycleGroup) .filter(([_, item]) => { return item.x !== undefined && item.y !== undefined }) .map(([key, item]) => { if (item.x!==null || item.y!==null){ @@ -197,8 +214,11 @@ export function getNodesPlacedInGroupCycle(subgraphNetwork:SubgraphNetwork,group return { id: key } } }); + }else{ + return []; + } }else{ - return null; + throw new Error("Cycle group not in subgraphNetwork"); } } @@ -212,18 +232,24 @@ export function getNodesPlacedInGroupCycle(subgraphNetwork:SubgraphNetwork,group * @returns An object representing the nodes placed in the group cycle. Each value contains the x and y coordinates. * 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]:{x:number,y:number }}{ - if (groupCycleID in subgraphNetwork[TypeSubgraph.CYCLEGROUP] && subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleID].precalculatedNodesPosition){ - return Object.entries(subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleID].precalculatedNodesPosition) +export function getNodesPlacedInGroupCycleAsObject(subgraphNetwork:SubgraphNetwork,groupCycleID:string):{[key:string]:Coordinate}{ + if (subgraphNetwork[TypeSubgraph.CYCLEGROUP] && groupCycleID in subgraphNetwork[TypeSubgraph.CYCLEGROUP]){ + const groupCycle =subgraphNetwork[TypeSubgraph.CYCLEGROUP][groupCycleID]; + if (groupCycle.precalculatedNodesPosition){ + const positionNodesCycleGroup=groupCycle.precalculatedNodesPosition; + return Object.entries(positionNodesCycleGroup) .filter(([_, item]) => { return item.x !== undefined && item.y !== undefined }) - .reduce((acc, node) => { + .reduce<{ [key:string]:Coordinate}>((acc, node) => { if (node[1].x!==null || node[1].y!==null){ acc[node[0]]={ x:node[1].x, y:node[1].y } } return acc; },{}); - }else{ - return null; + }else{ + return {}; + } + } else { + throw new Error("Cycle group not in subgraphNetwork"); } } @@ -318,7 +344,7 @@ export function sortLinksWithAllGroupCycle(subgraphNetwork:SubgraphNetwork,order */ function sortLinksWithGroupCycle(subgraphNetwork:SubgraphNetwork,groupCycle:string):{subgraphNetwork:SubgraphNetwork,linksOrdered:LinkLayout[]}{ let links:LinkLayout[]=[]; - if( groupCycle in subgraphNetwork.cyclesGroup){ + 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); @@ -448,30 +474,10 @@ export function getListNodeLinksForCycleGroupAsObject(subgraphNetwork:SubgraphNe * @returns the name of the parent cycle of the cycle, if not, return the name of the cycle */ export function parentCycle(cycleName:string,subgraphNetwork:SubgraphNetwork):string{ + if (!subgraphNetwork[TypeSubgraph.CYCLE] || !subgraphNetwork[TypeSubgraph.CYCLE][cycleName]) throw new Error("cycle not in subgraphNetwork"); if (subgraphNetwork[TypeSubgraph.CYCLE][cycleName].parentSubgraph && subgraphNetwork[TypeSubgraph.CYCLE][cycleName].parentSubgraph.type==TypeSubgraph.CYCLE){ return subgraphNetwork[TypeSubgraph.CYCLE][cycleName].parentSubgraph.name; }else{ return cycleName; } -} - - - - -// function getLinkParent2GroupCycle(subgraphNetwork:SubgraphNetwork,parentId:string,groupCycle:string){ -// return Object.values(subgraphNetwork.network.value.links).filter((link) => { -// return link.source.id === parentId && link.target.metadata && TypeSubgraph.CYCLEGROUP in link.target.metadata && link.target.metadata[TypeSubgraph.CYCLEGROUP] === groupCycle; -// }); -// } - -// function listOfCycles(cycleList:string[],subgraphNetwork:SubgraphNetwork):string[]{ -// const newCyclesList=[]; -// cycleList.forEach(cycle=>{ -// // if cycle is a 'child' of another cycle : no new metanode, it is considered as the parent cycle metanode (else it is the cycle) -// const biggerCycle=inBiggerCycle(cycle,subgraphNetwork); -// if (!newCyclesList.includes(biggerCycle)){ -// newCyclesList.push(biggerCycle); -// } -// }); -// return newCyclesList; -// } +} \ No newline at end of file diff --git a/src/composables/CalculateStartNodes.ts b/src/composables/CalculateStartNodes.ts index e46dd26..bc9a4e4 100644 --- a/src/composables/CalculateStartNodes.ts +++ b/src/composables/CalculateStartNodes.ts @@ -1,7 +1,7 @@ // Types imports import { StartNodesType } from "../types/EnumArgs"; import { Network } from "@metabohub/viz-core/src/types/Network"; -import { NodeLayout } from "../types/NetworkLayout"; +import { NetworkLayout, NodeLayout } from "../types/NetworkLayout"; // Composable imports import { networkToGDSGraph } from "./ConvertFromNetwork"; @@ -36,12 +36,16 @@ import { networkToGDSGraph } from "./ConvertFromNetwork"; * */ + /** - * Take network and all the unique y coordinate. Add the rank (y position : first, second...; not coordinate) and order ( x position in the rank: first, second,....) to metadata of network. - * @param {Network} Network object - * @param unique_y array of all unique y for node position + * Assigns rank and order to nodes in a network layout based on their x and y coordinates. + * + * @param network - The network object. + * @param unique_y - An array of unique y coordinates that indicates the y that will be associated with rank. + * If a node in the network have a "y" not in this array, it will not be assigned a rank (and order) + * @param onlyRank - Optional parameter to indicate whether to assign only the rank or both rank and order. Default is true. */ -export function assignRankOrder(network: Network, unique_y: Array<number>):void { +export function assignRankOrder(network: NetworkLayout, unique_y: Array<number>,onlyRank:boolean=true):void { // sort the y to know the associated rank for a y coordinate unique_y.sort((a:number, b:number) => a - b); @@ -50,15 +54,17 @@ export function assignRankOrder(network: Network, unique_y: Array<number>):void const xNodeByRank: number[][] = Array.from({ length: unique_y.length }, () => []); Object.values(network.nodes).forEach((node) => { const rank = unique_y.indexOf(node.y); + if (!node.metadataLayout) node.metadataLayout={}; if(rank >-1){ - node.metadata = node.metadata || {}; - node.metadata.rank = rank; - xNodeByRank[rank].push(node.x); - }else{ - node.metadata.rank = undefined; + node.metadataLayout.rank = rank; + if (!onlyRank){ + xNodeByRank[rank].push(node.x); + } } }); + if (onlyRank) return; + // sort the y by rank xNodeByRank.forEach(sublist => { sublist.sort((a, b) => a - b); @@ -66,17 +72,12 @@ export function assignRankOrder(network: Network, unique_y: Array<number>):void // get the order for each node Object.values(network.nodes).forEach((node) => { - if (node.metadata && Object.keys(node.metadata).includes("rank") && node.metadata.rank !== undefined){ - const rank = node.metadata.rank; - if (typeof rank === 'number') { + if (node.metadataLayout && node.metadataLayout.rank){ + const rank = node.metadataLayout.rank; + if (rank<xNodeByRank.length){ const order = xNodeByRank[rank].indexOf(node.x); - node.metadata.order = order; - } else { - console.error("Le rang n'est pas un nombre"); - node.metadata.order = undefined; + node.metadataLayout.order = order; } - }else{ - node.metadata.order = undefined; } }); } @@ -101,9 +102,9 @@ export async function getStartNodes(network:Network, typeSource:StartNodesType): return Object.keys(network.nodes).sort(); } - const start_rank=[]; - const start_source=[]; - const start_all=[]; + const start_rank:string[]=[]; + const start_source:string[]=[]; + const start_all:string[]=[]; // get object for data-graph-structure if indegree information needed (when source nodes needed) let graph:{[key:string]:Function}; @@ -130,25 +131,13 @@ export async function getStartNodes(network:Network, typeSource:StartNodesType): } -// function getSourcesParam(network:Network,sourceType:SourceType):string[]{ -// let sources:string[]=[]; -// if(onlyUserSources){ -// sources=userSources; -// }else{ -// sources = concatSources(userSources as string[],getSources(network,sourceType)); -// } -// return sources; -// } - - /** * Node has rank 0 in metadata ? * @param node Node * @returns boolean */ function hasRank0(node:NodeLayout):boolean{ - console.warn('change rank attr'); - return (node.metadata && Object.keys(node.metadata).includes("rank") && node.metadata.rank===0); + return node.metadataLayout?.rank === 0; } /** diff --git a/src/composables/ConvertFromNetwork.ts b/src/composables/ConvertFromNetwork.ts index 90240fc..cf4cc22 100644 --- a/src/composables/ConvertFromNetwork.ts +++ b/src/composables/ConvertFromNetwork.ts @@ -14,14 +14,14 @@ import { cycleMetanodeLink, sortLinksWithAllGroupCycle } from './CalculateRelati // General imports -import dagre from 'dagrejs/dist/dagre.js'; +//import dagre from 'dagrejs/dist/dagre.js'; import { Graph } from "@viz-js/viz"; import * as GDS from 'graph-data-structure'; import { h } from 'vue'; import { link } from 'fs'; -import { s } from 'vitest/dist/reporters-1evA5lom'; +//import { s } from 'vitest/dist/reporters-1evA5lom'; import { get } from 'http'; -import cytoscape, { ElementDefinition,Stylesheet } from 'cytoscape'; +//import cytoscape, { ElementDefinition,Stylesheet } from 'cytoscape'; import { layout } from 'dagrejs'; import { dot } from 'node:test/reporters'; @@ -173,28 +173,28 @@ export function networkToAdjacentObject(network:Network):{[key : string]:string[ * @param graphAttributes for dagre layout (see https://github.com/dagrejs/dagre/wiki) * @returns {dagre.graphlib.Graph} Return dagre.graphlib.Graph object */ -export function networkToDagre(network: Network,graphAttributes={}): dagre.graphlib.Graph{ +// export function networkToDagre(network: Network,graphAttributes={}): dagre.graphlib.Graph{ - // initialisation dagre graph - var g = new dagre.graphlib.Graph(); - g.setGraph(graphAttributes); - g.setDefaultEdgeLabel(() => ({})); +// // initialisation dagre graph +// var g = new dagre.graphlib.Graph(); +// g.setGraph(graphAttributes); +// g.setDefaultEdgeLabel(() => ({})); - // insert nodes into graph - Object.values(network.nodes).forEach((node) => { - const { id, label, x, y } = node; - g.setNode(id, { label, width: 100, height: 100, x, y }); - }); +// // insert nodes into graph +// Object.values(network.nodes).forEach((node) => { +// const { id, label, x, y } = node; +// g.setNode(id, { label, width: 100, height: 100, x, y }); +// }); - // insert edges into graph - network.links.forEach((link) => { - const { source, target } = link; - g.setEdge(source.id, target.id); - }); +// // insert edges into graph +// network.links.forEach((link) => { +// const { source, target } = link; +// g.setEdge(source.id, target.id); +// }); - return g; +// return g; -} +// } @@ -205,47 +205,47 @@ export function networkToDagre(network: Network,graphAttributes={}): dagre.graph * @param initialPosition - Optional. Specifies whether to initialize the position of the nodes. Default is false. * @returns The converted Cytoscape object. */ -export function networkToCytoscape(network: Network, initialPosition:boolean=false): cytoscape.Core { - - // Convert nodes - const nodes: ElementDefinition[] = Object.values(network.nodes).map(node => ({ - data: { - id: node.id, - }, - position: { - x: node.x, - y: node.y, - }, - })); +// export function networkToCytoscape(network: Network, initialPosition:boolean=false): cytoscape.Core { + +// // Convert nodes +// const nodes: ElementDefinition[] = Object.values(network.nodes).map(node => ({ +// data: { +// id: node.id, +// }, +// position: { +// x: node.x, +// y: node.y, +// }, +// })); - // Convert links - const edges: ElementDefinition[] = []; - network.links.forEach(link => { - edges.push({ - data: { - id: link.id, - source: link.source.id, - target: link.target.id, - } - }); - }); +// // Convert links +// const edges: ElementDefinition[] = []; +// network.links.forEach(link => { +// edges.push({ +// data: { +// id: link.id, +// source: link.source.id, +// target: link.target.id, +// } +// }); +// }); - if (initialPosition){ - return cytoscape({ - container: undefined, - elements: {nodes:nodes, edges:edges}, - layout: { - name: 'preset', // to initialize the position of the nodes - }, - }); - }else{ - return cytoscape({ - container: undefined, - elements: {nodes:nodes, edges:edges}, - }); - } -} +// if (initialPosition){ +// return cytoscape({ +// container: undefined, +// elements: {nodes:nodes, edges:edges}, +// layout: { +// name: 'preset', // to initialize the position of the nodes +// }, +// }); +// }else{ +// return cytoscape({ +// container: undefined, +// elements: {nodes:nodes, edges:edges}, +// }); +// } +// } /** @@ -294,7 +294,7 @@ export function networkToViz(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) .forEach(([key, node])=> { const nodeViz=nodeForViz(subgraphNetwork,node,cycle,groupOrCluster); - if (nodeViz && !graphViz.nodes.some(node => node.name === nodeViz.name)){ + if (nodeViz && graphViz.nodes && !graphViz.nodes.some(node => node.name === nodeViz.name)){ graphViz.nodes.push(nodeViz); } }); @@ -308,7 +308,7 @@ export function networkToViz(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, links.forEach((link)=>{ // get tail and head (take into account cycle metanode) const {tail,head}=cycleMetanodeLink(link,cycle); - if (tail!==head && !graphViz.edges.some(edge => edge.tail === tail && edge.head === head)){ + if (tail!==head && graphViz.edges && !graphViz.edges.some(edge => edge.tail === tail && edge.head === head)){ graphViz.edges.push({ tail: tail, head: head, @@ -318,8 +318,9 @@ export function networkToViz(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, // insert mainChain cluster - if (groupOrCluster==="cluster" && Object.keys(subgraphNetwork).includes(TypeSubgraph.MAIN_CHAIN)){ - Object.keys(subgraphNetwork[TypeSubgraph.MAIN_CHAIN]).sort((a, b) => subgraphNetwork[TypeSubgraph.MAIN_CHAIN][b].nodes.length - subgraphNetwork[TypeSubgraph.MAIN_CHAIN][a].nodes.length) // sort depending on size : bigger first + if (groupOrCluster==="cluster" && subgraphNetwork[TypeSubgraph.MAIN_CHAIN]){ + const mainChain = subgraphNetwork[TypeSubgraph.MAIN_CHAIN]; + Object.keys(mainChain).sort((a, b) => mainChain[b].nodes.length - mainChain[a].nodes.length) // sort depending on size : bigger first .forEach((nameMainChain) => { graphViz=addMainChainClusterViz(graphViz,nameMainChain,subgraphNetwork,cycle); }); @@ -327,19 +328,41 @@ export function networkToViz(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, // insert cycle metanode if (cycle && subgraphNetwork[TypeSubgraph.CYCLEGROUP]){ - Object.values(subgraphNetwork[TypeSubgraph.CYCLEGROUP]).sort((a, b) => { // sort depending on size : bigger first - const areaB = b.width * b.height; - const areaA = a.width * a.height; - return areaB - areaA; + const cycleGroup=subgraphNetwork[TypeSubgraph.CYCLEGROUP]; + Object.values(cycleGroup).sort((a, b) => { // sort depending on size : bigger first + const widthA = a?.width; + const widthB = b?.width; + const heightA = a?.height; + const heightB = b?.height; + if (widthA && widthB && heightA && heightB){ + const areaB = widthB * heightB; + const areaA = widthA * heightA; + return areaB - areaA; + }else{ + throw new Error("Cycle group without width or height"); + } }) .forEach((cycleGroup) => { - const height=pixelsToInches(cycleGroup.height); - const width=pixelsToInches(cycleGroup.width); - let ordering=Ordering.DEFAULT; - if(orderChange){ - ordering=cycleGroup.ordering; + const attributes:AttributesViz={fixedsize:true}; + + const heightPixel=cycleGroup?.height; + const widthPixel=cycleGroup?.width; + if(heightPixel && widthPixel){ + const height=pixelsToInches(heightPixel); + const width=pixelsToInches(widthPixel); + attributes.height=height; + attributes.width=width; + }else{ + throw new Error("Cycle group without width or height"); } - graphViz.nodes.push({name:cycleGroup.name, attributes:{height:height,width:width,ordering:ordering,fixedsize:true}}); + + if(orderChange && cycleGroup.ordering){ + attributes.ordering=cycleGroup.ordering; + }else{ + throw new Error("Demand of change order but cycle group without ordering"); + } + if (!graphViz.nodes) graphViz.nodes=[]; + graphViz.nodes.push({name:cycleGroup.name, attributes:attributes}); }); } return graphViz; @@ -354,7 +377,7 @@ export function networkToViz(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, * @param groupOrCluster - Specifies whether the node belongs to a group or cluster. * @returns An object containing the name and attributes of the converted node. */ -function nodeForViz(subgraphNetwork:SubgraphNetwork,node:NodeLayout,cycle:boolean,groupOrCluster:"group"|"cluster"):{name:string,attributes:AttributesViz}{ +function nodeForViz(subgraphNetwork:SubgraphNetwork,node:NodeLayout,cycle:boolean,groupOrCluster:"group"|"cluster"):{name:string,attributes:AttributesViz}|undefined{ // if cycle not taken into account or not in cycle if (!cycle || !inCycle(subgraphNetwork.network.value,node.id)){ const attributes:AttributesViz={}; @@ -389,54 +412,67 @@ function nodeForViz(subgraphNetwork:SubgraphNetwork,node:NodeLayout,cycle:boolea */ export function graphVizToDot(vizGraph:Graph, subgraphFirst:boolean=true):string{ // initialisation viz graph with graph attributs - let dotString="strict digraph G {\n graph "+customStringify(vizGraph.graphAttributes)+"\n"; + let dotString="strict digraph G {\n graph "+customStringify(vizGraph.graphAttributes as AttributesViz)+"\n"; if (subgraphFirst){ // clusters - vizGraph.subgraphs.forEach((subgraph) => { - dotString+=addClusterDot(subgraph as SubgraphViz); - }); + if (vizGraph.subgraphs) { + vizGraph.subgraphs.forEach((subgraph) => { + dotString+=addClusterDot(subgraph as SubgraphViz); + }); + } // nodes - vizGraph.nodes.forEach((node) => { - const nodeAttributes= customStringify(node.attributes); - dotString+=`${node.name} ${nodeAttributes};\n`; - }); + if (vizGraph.nodes){ + vizGraph.nodes.forEach((node) => { + const nodeAttributes= customStringify(node.attributes as AttributesViz); + dotString+=`${node.name} ${nodeAttributes};\n`; + }); + } // edges - vizGraph.edges.forEach((edge) => { - dotString+=`${edge.tail} -> ${edge.head};\n`; - }); + if (vizGraph.edges){ + vizGraph.edges.forEach((edge) => { + dotString+=`${edge.tail} -> ${edge.head};\n`; + }); + } } else { // nodes - vizGraph.nodes.forEach((node) => { - const nodeAttributes= customStringify(node.attributes); - dotString+=`${node.name} ${nodeAttributes};\n`; - }); + if (vizGraph.nodes){ + vizGraph.nodes.forEach((node) => { + const nodeAttributes= customStringify(node.attributes as AttributesViz); + dotString+=`${node.name} ${nodeAttributes};\n`; + }); + } // edges - vizGraph.edges.forEach((edge) => { - dotString+=`${edge.tail} -> ${edge.head};\n`; - }); + if (vizGraph.edges){ + vizGraph.edges.forEach((edge) => { + dotString+=`${edge.tail} -> ${edge.head};\n`; + }); + } // clusters - vizGraph.subgraphs.forEach((subgraph) => { - dotString+=addClusterDot(subgraph as SubgraphViz); - }); + if (vizGraph.subgraphs) { + vizGraph.subgraphs.forEach((subgraph) => { + dotString+=addClusterDot(subgraph as SubgraphViz); + }); + } } return dotString+"}"; } + /** - * Converts an object into a custom string representation. + * Converts an object to a string representation with attribute-value pairs. * - * @param obj - The object to be converted. - * @returns The custom string representation of the object. + * @param obj - The object to convert. + * @returns A string representation of the object with attribute-value pairs. */ -function customStringify(obj) { - if (Object.keys(obj).length === 0) { +function customStringify(obj:AttributesViz|undefined) { + if (!obj || Object.keys(obj).length === 0) { return ""; } let str = '['; @@ -446,37 +482,4 @@ function customStringify(obj) { str = str.slice(0, -2); // remove trailing comma and space str += ']'; return str; -} - - - - - -// /** -// * Return a copy of the network -// * @param network -// * @returns a copy of the network -// */ -// export function networkCopy(network: Network): Network { -// const newNetwork: Network = { -// id: network.id, -// label: network.label, -// nodes: {}, -// links: [] -// }; - -// Object.keys(network.nodes).forEach(key=>{ -// newNetwork.nodes[key] = Object.assign({}, network.nodes[key]); -// }) - -// network.links.forEach(item=>{ -// //get all info from links -// const newlink=Object.assign({}, item); -// // update the node to have a pointeur -// newlink.source=newNetwork.nodes[item.source.id]; -// newlink.target=newNetwork.nodes[item.target.id]; -// newNetwork.links.push(newlink); -// }); -// return newNetwork; -// } - +} \ No newline at end of file diff --git a/src/composables/ConvertToNetwork.ts b/src/composables/ConvertToNetwork.ts index 2367645..76bd682 100644 --- a/src/composables/ConvertToNetwork.ts +++ b/src/composables/ConvertToNetwork.ts @@ -11,8 +11,8 @@ import { assignRankOrder } from './CalculateStartNodes'; import { getSizeNodePixel } from './CalculateSize'; // General imports -import cytoscape, { ElementDefinition } from 'cytoscape'; -import dagre from 'dagrejs/dist/dagre.js'; +// import cytoscape, { ElementDefinition } from 'cytoscape'; +// import dagre from 'dagrejs/dist/dagre.js'; import { type } from 'os'; import { TypeSubgraph } from '../types/Subgraph'; @@ -53,31 +53,31 @@ export function networkLayoutToNetwork(networkLayout: NetworkLayout): Network { } -/** - * Take dagre.graphlib.Graph object and the network associated (with the graph) : change the position and metadata (rank and order) of network's node by the one of the graph. - * The graph and network need to have the same nodes ! - * @param {dagre.graphlib.Graph} dagre.graphlib.Graph object - * @param {Network} Network object (value of pointer) - */ -export async function changeNetworkFromDagre(graph: dagre.graphlib.Graph,network: NetworkLayout): Promise<void>{ - Object.entries(graph["_nodes"]).forEach(([node, nodeData]:[string, dagre.graphlib.Node]) => { - if (!network.nodes[node].metadataLayout) { - network.nodes[node].metadataLayout = {}; - } - const { x, y, _order,_rank } = nodeData; - // if there is some position x and y : network is updated - if (x !== undefined && y !== undefined){ - if (network.nodes[node]) { - network.nodes[node].x = x; - network.nodes[node].y = y; - } else { - throw new Error(`Node '${node}' not found in the network.`); - } - network.nodes[node].metadataLayout.order = _order; - network.nodes[node].metadataLayout.rank = _rank / 2; // because rank 0 is 0, and the next is 2, 4, ... - } - }); -} +// /** +// * Take dagre.graphlib.Graph object and the network associated (with the graph) : change the position and metadata (rank and order) of network's node by the one of the graph. +// * The graph and network need to have the same nodes ! +// * @param {dagre.graphlib.Graph} dagre.graphlib.Graph object +// * @param {Network} Network object (value of pointer) +// */ +// export async function changeNetworkFromDagre(graph: dagre.graphlib.Graph,network: NetworkLayout): Promise<void>{ +// Object.entries(graph["_nodes"]).forEach(([node, nodeData]:[string, dagre.graphlib.Node]) => { +// if (!network.nodes[node].metadataLayout) { +// network.nodes[node].metadataLayout = {}; +// } +// const { x, y, _order,_rank } = nodeData; +// // if there is some position x and y : network is updated +// if (x !== undefined && y !== undefined){ +// if (network.nodes[node]) { +// network.nodes[node].x = x; +// network.nodes[node].y = y; +// } else { +// throw new Error(`Node '${node}' not found in the network.`); +// } +// network.nodes[node].metadataLayout.order = _order; +// network.nodes[node].metadataLayout.rank = _rank / 2; // because rank 0 is 0, and the next is 2, 4, ... +// } +// }); +// } /** @@ -95,8 +95,8 @@ export async function changeNetworkFromViz(json: JsonViz, subgraphNetwork: Subgr const nodeId = node.name; // if node is a 'classic' node - if (nodeId in network.nodes && 'pos' in node){ - const pos = node.pos.split(','); + if (nodeId in network.nodes && Object.keys(node).includes("pos")){ + const pos = node["pos"]?.split(',') ?? []; let x = parseFloat(pos[0]); let y = parseFloat(pos[1]); @@ -107,15 +107,13 @@ export async function changeNetworkFromViz(json: JsonViz, subgraphNetwork: Subgr unique_y.push(y); } - }else if (subgraphNetwork[TypeSubgraph.CYCLEGROUP] && nodeId in subgraphNetwork[TypeSubgraph.CYCLEGROUP] && 'pos' in node){ + }else if (subgraphNetwork[TypeSubgraph.CYCLEGROUP] && nodeId in subgraphNetwork[TypeSubgraph.CYCLEGROUP] && Object.keys(node).includes("pos")){ //if node is a cycle metanode - const pos = node.pos.split(','); + const pos = node["pos"]?.split(',') ?? []; const x = parseFloat(pos[0]); const y = parseFloat(pos[1]); - // if (!subgraphNetwork[TypeSubgraph.CYCLEGROUP][nodeId].metadata){ - // subgraphNetwork[TypeSubgraph.CYCLEGROUP][nodeId].metadata={}; - // } + subgraphNetwork[TypeSubgraph.CYCLEGROUP][nodeId].position={x:x,y:y}; }else{ @@ -131,24 +129,24 @@ export async function changeNetworkFromViz(json: JsonViz, subgraphNetwork: Subgr } -/** - * Updates the positions of nodes in the network based on the provided Cytoscape JSON. - * - * @param jsonCytoscape - The Cytoscape JSON containing the elements and their positions. - * @param network - The network to update. - */ -export function changeNetworkFromCytoscape(jsonCytoscape: {elements: { nodes:ElementDefinition[] } }, network:Network) : void { - - jsonCytoscape.elements.nodes.forEach((node: any) => { - const idNode= node.data.id; - if (Object.keys(network.nodes).includes(idNode)) { - network.nodes[idNode].x = node.position.x; - network.nodes[idNode].y = node.position.y; - }else{ - throw new Error(`Node '${idNode}' not found in the network.`); - } - }); -} +// /** +// * Updates the positions of nodes in the network based on the provided Cytoscape JSON. +// * +// * @param jsonCytoscape - The Cytoscape JSON containing the elements and their positions. +// * @param network - The network to update. +// */ +// export function changeNetworkFromCytoscape(jsonCytoscape: {elements: { nodes:ElementDefinition[] } }, network:Network) : void { + +// jsonCytoscape.elements.nodes.forEach((node: any) => { +// const idNode= node.data.id; +// if (Object.keys(network.nodes).includes(idNode)) { +// network.nodes[idNode].x = node.position.x; +// network.nodes[idNode].y = node.position.y; +// }else{ +// throw new Error(`Node '${idNode}' not found in the network.`); +// } +// }); +// } diff --git a/src/composables/LayoutManageSideCompounds.ts b/src/composables/LayoutManageSideCompounds.ts index 3422389..6809e85 100644 --- a/src/composables/LayoutManageSideCompounds.ts +++ b/src/composables/LayoutManageSideCompounds.ts @@ -14,7 +14,7 @@ import { getMeanNodesSizePixel, inchesToPixels, minEdgeLength as minEdgeLength, import { sideCompoundAttribute,isDuplicate, isReaction, isSideCompound, setAsSideCompound } from "./GetSetAttributsNodes"; // General imports -import { e, S } from "vitest/dist/reporters-1evA5lom"; +//import { e, S } from "vitest/dist/reporters-1evA5lom"; import { c } from "vite/dist/node/types.d-aGj9QkWt"; import { resolve } from "path"; import { error } from "console"; @@ -269,24 +269,25 @@ function removeSideCompoundsFromNetwork(subgraphNetwork:SubgraphNetwork): Subgra const network = subgraphNetwork.network.value; subgraphNetwork.sideCompounds={}; + const sideCompounds = subgraphNetwork.sideCompounds; const listIDCoftactors:string[]=[]; // for each link, see if the source or target is in the list of side compounds : add information in subgraphNetwork.sideCompounds network.links.forEach((link) => { // if source is side compound if (isSideCompound(link.source)) { - if(!(link.target.id in subgraphNetwork.sideCompounds)){ - subgraphNetwork.sideCompounds[link.target.id]={reactants:[],products:[]}; + if(!(link.target.id in sideCompounds)){ + sideCompounds[link.target.id]={reactants:[],products:[]}; } - subgraphNetwork.sideCompounds[link.target.id].reactants.push(link.source); + sideCompounds[link.target.id].reactants.push(link.source); listIDCoftactors.push(link.source.id); } // if target is side compound if (isSideCompound(link.target)) { - if(!(link.source.id in subgraphNetwork.sideCompounds)){ - subgraphNetwork.sideCompounds[link.source.id]={reactants:[],products:[]}; + if(!(link.source.id in sideCompounds)){ + sideCompounds[link.source.id]={reactants:[],products:[]}; } - subgraphNetwork.sideCompounds[link.source.id].products.push(link.target); + sideCompounds[link.source.id].products.push(link.target); listIDCoftactors.push(link.target.id); } }); @@ -310,6 +311,7 @@ function removeSideCompoundsFromNetwork(subgraphNetwork:SubgraphNetwork): Subgra */ export async function reinsertionSideCompounds(subgraphNetwork:SubgraphNetwork,factorMinEdgeLength:number=1/2,doReactionReversible:boolean):Promise<SubgraphNetwork>{ if(subgraphNetwork.sideCompounds){ + const sideCompounds = subgraphNetwork.sideCompounds // get information for length of edge for side compounds : // get the min length of edge in the network (if not, use default value) let minLength=minEdgeLength(subgraphNetwork.network.value,false); @@ -328,7 +330,7 @@ export async function reinsertionSideCompounds(subgraphNetwork:SubgraphNetwork,f } // for each reaction, apply motif stamp - Object.keys(subgraphNetwork.sideCompounds).forEach( async (reactionID)=>{ + Object.keys(sideCompounds).forEach( async (reactionID)=>{ subgraphNetwork= await motifStampSideCompound(subgraphNetwork,reactionID,factorMinEdgeLength); }); } @@ -348,10 +350,13 @@ async function minEdgeLengthDefault(subgraphNetwork:SubgraphNetwork,factorMinEdg const networkStyle = subgraphNetwork.networkStyle.value; const meanSize=await getMeanNodesSizePixel(Object.values(network.nodes),networkStyle, false); const defaultSep=(meanSize.height+meanSize.width)/2; - const rankSep=subgraphNetwork.attributs[VizArgs.RANKSEP]?inchesToPixels(subgraphNetwork.attributs[VizArgs.RANKSEP] as number):defaultSep; - const nodeSep=subgraphNetwork.attributs[VizArgs.NODESEP]?inchesToPixels(subgraphNetwork.attributs[VizArgs.NODESEP] as number):defaultSep; - //const invFactor=factorMinEdgeLength===0?1:1/factorMinEdgeLength; - return (nodeSep+rankSep)/2*factorMinEdgeLength; + if(subgraphNetwork.attributs){ + const rankSep=subgraphNetwork.attributs[VizArgs.RANKSEP]?inchesToPixels(subgraphNetwork.attributs[VizArgs.RANKSEP] as number):defaultSep; + const nodeSep=subgraphNetwork.attributs[VizArgs.NODESEP]?inchesToPixels(subgraphNetwork.attributs[VizArgs.NODESEP] as number):defaultSep; + return (nodeSep+rankSep)/2*factorMinEdgeLength; + }else{ + return defaultSep * factorMinEdgeLength; + } } /** @@ -361,16 +366,19 @@ async function minEdgeLengthDefault(subgraphNetwork:SubgraphNetwork,factorMinEdg */ async function updateSideCompoundsReversibleReaction(subgraphNetwork:SubgraphNetwork):Promise<SubgraphNetwork>{ const network = subgraphNetwork.network.value; - Object.keys(subgraphNetwork.sideCompounds).forEach((reactionID)=>{ - if (!(reactionID in network.nodes)) throw new Error("Reaction not in subgraphNetwork") - // if reaction has been reversed : exchange products and reactants - if(network.nodes[reactionID].metadataLayout && network.nodes[reactionID].metadataLayout.isReversedVersion){ - const products=subgraphNetwork.sideCompounds[reactionID].products; - const reactants=subgraphNetwork.sideCompounds[reactionID].reactants; - subgraphNetwork.sideCompounds[reactionID].products=reactants; - subgraphNetwork.sideCompounds[reactionID].reactants=products; - } - }); + if (subgraphNetwork.sideCompounds){ + let sideCompounds=subgraphNetwork.sideCompounds; + Object.keys(sideCompounds).forEach((reactionID)=>{ + if (!(reactionID in network.nodes)) throw new Error("Reaction not in subgraphNetwork") + // if reaction has been reversed : exchange products and reactants + if(network.nodes[reactionID].metadataLayout && network.nodes[reactionID].metadataLayout.isReversedVersion){ + const products=sideCompounds[reactionID].products; + const reactants=sideCompounds[reactionID].reactants; + sideCompounds[reactionID].products=reactants; + sideCompounds[reactionID].reactants=products; + } + }); + } return subgraphNetwork; } diff --git a/src/composables/LayoutReversibleReactions.ts b/src/composables/LayoutReversibleReactions.ts index d2faf2c..21c36c3 100644 --- a/src/composables/LayoutReversibleReactions.ts +++ b/src/composables/LayoutReversibleReactions.ts @@ -13,7 +13,7 @@ import { BFSWithSources } from "./AlgorithmBFS"; import { addLinkClassReversible, addMetadataReversibleWithClass, isReaction, isReversible } from "./GetSetAttributsNodes"; // General imports -import { e } from "vitest/dist/reporters-1evA5lom"; +//import { e } from "vitest/dist/reporters-1evA5lom"; import { l } from "vite/dist/node/types.d-aGj9QkWt"; @@ -91,7 +91,7 @@ export async function duplicateReversibleReactions(networkLayout:NetworkLayout): if (linkReversible.isReversible) { // Duplication of the reaction node let nodeToDuplicate: NodeLayout; - const reactionIsSource: boolean = linkReversible.sourceIsReversible; + const reactionIsSource: boolean|undefined = linkReversible.sourceIsReversible; if (reactionIsSource) { nodeToDuplicate = link.source; @@ -135,7 +135,7 @@ export async function duplicateReversibleReactions(networkLayout:NetworkLayout): * - `isReversible` - A boolean indicating if the link is reversible. * - `sourceIsReversible` - A boolean indicating if it's the source of the link that is reversible. */ -function linkIsReversible(network:Network,link: Link): {isReversible:boolean,sourceIsReversible:boolean}{ +function linkIsReversible(network:Network,link: Link): {isReversible:boolean,sourceIsReversible:boolean|undefined}{ if (isReaction(link.source) && isReversible(network,link.source.id)){ return {isReversible:true,sourceIsReversible:true}; } else if (isReaction(link.target) && isReversible(network,link.target.id)){ @@ -164,10 +164,11 @@ async function duplicateNodeReactionReversible(networkLayout: NetworkLayout, nod throw new Error("Node not found to set as reversible"); } - if (!networkLayout.nodes[nodeReaction.id].metadataLayout) { - networkLayout.nodes[nodeReaction.id].metadataLayout = {}; + if (networkLayout.nodes[nodeReaction.id].metadataLayout) { + networkLayout.nodes[nodeReaction.id].metadataLayout.reversibleNodeVersion = newId; + }else{ + networkLayout.nodes[nodeReaction.id].metadataLayout = {reversibleNodeVersion:newId}; } - networkLayout.nodes[nodeReaction.id].metadataLayout.reversibleNodeVersion = newId; resolve(newReactionNode); } catch (error) { diff --git a/src/composables/__tests__/CalculateStartNodes.tests.ts b/src/composables/__tests__/CalculateStartNodes.tests.ts new file mode 100644 index 0000000..de95410 --- /dev/null +++ b/src/composables/__tests__/CalculateStartNodes.tests.ts @@ -0,0 +1,102 @@ +// Type imports +import { StartNodesType } from '../../types/EnumArgs'; +import { NetworkLayout, NodeLayout } from '../../types/NetworkLayout'; +import { Network } from '@metabohub/viz-core/src/types/Network'; + +// Composable imports +import * as CalculateStartNodes from '../CalculateStartNodes'; + + +describe('assignRankOrder', () => { + let networkLayout: NetworkLayout; + + beforeEach(() => { + networkLayout = { + id:"test", + nodes: { + node1: { id: 'node1', x: 1, y: 2, metadataLayout: {} }, + node2: { id: 'node2', x: 2, y: 3, metadataLayout: {} }, + node3: { id: 'node3', x: 3, y: 2, metadataLayout: {} }, + node4: { id: 'node4', x: 3, y: 1, metadataLayout: {} }, + }, + links: [] + }; + + }); + + + it('should assign rank and order to nodes based on their coordinates', () => { + // DATA + const unique_y = [1,2, 3]; + const networkExpected: NetworkLayout = { + id:"test", + nodes: { + node1: { id: 'node1', x: 1, y: 2, metadataLayout: {rank:1,order:0} }, + node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:2,order:0} }, + node3: { id: 'node3', x: 3, y: 2, metadataLayout: {rank:1,order:1} }, + node4: { id: 'node4', x: 3, y: 1, metadataLayout: {rank:0,order:0} }, + }, + links: [] + }; + + // TEST + CalculateStartNodes.assignRankOrder(networkLayout, unique_y, false); + + // EXPECT + expect(networkLayout).toEqual(networkExpected); + }); + + + + // it('should assign only rank if onlyRank is true', () => { + // const unique_y = [2, 3]; + // assignRankOrder(networkLayout, unique_y, true); + + // expect(networkLayout.nodes.node1.metadataLayout.rank).toBe(0); + // expect(networkLayout.nodes.node1.metadataLayout.order).toBeUndefined(); + // expect(networkLayout.nodes.node2.metadataLayout.rank).toBe(1); + // expect(networkLayout.nodes.node2.metadataLayout.order).toBeUndefined(); + // expect(networkLayout.nodes.node3.metadataLayout.rank).toBe(0); + // expect(networkLayout.nodes.node3.metadataLayout.order).toBeUndefined(); + // }); +}); + +// describe('getStartNodes', () => { +// it('should return all nodes if typeSource is ALL', async () => { +// const result = await getStartNodes(mockNetwork, StartNodesType.ALL); +// expect(result).toEqual(['node1', 'node2', 'node3']); +// }); + +// it('should return nodes with rank 0 if typeSource is RANK_ONLY', async () => { +// const result = await getStartNodes(mockNetwork, StartNodesType.RANK_ONLY); +// expect(result).toEqual(['node1']); +// }); + +// // Add more tests for other StartNodesType values as needed +// }); + +// describe('concatSources', () => { +// it('should concatenate two arrays and remove duplicates', () => { +// const firstSources = ['a', 'b', 'c']; +// const secondSources = ['b', 'c', 'd']; +// const result = concatSources(firstSources, secondSources); + +// expect(result).toEqual(['a', 'b', 'c', 'd']); +// }); + +// it('should return the first array if the second array is empty', () => { +// const firstSources = ['a', 'b', 'c']; +// const secondSources: string[] = []; +// const result = concatSources(firstSources, secondSources); + +// expect(result).toEqual(['a', 'b', 'c']); +// }); + +// it('should return the second array if the first array is empty', () => { +// const firstSources: string[] = []; +// const secondSources = ['a', 'b', 'c']; +// const result = concatSources(firstSources, secondSources); + +// expect(result).toEqual(['a', 'b', 'c']); +// }); +// }); \ No newline at end of file -- GitLab From 95c3001bcc97398a5c3b478287ad3703a9d89a89 Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Thu, 12 Sep 2024 13:56:03 +0200 Subject: [PATCH 09/13] re clean de fichier avec nouveau linter --- src/composables/LayoutManageSideCompounds.ts | 59 ++++++---- src/composables/LayoutReversibleReactions.ts | 2 +- src/composables/LayoutSugiyamaForce.ts | 109 +++++++++---------- src/types/Reaction.ts | 4 +- 4 files changed, 93 insertions(+), 81 deletions(-) diff --git a/src/composables/LayoutManageSideCompounds.ts b/src/composables/LayoutManageSideCompounds.ts index 6809e85..186395c 100644 --- a/src/composables/LayoutManageSideCompounds.ts +++ b/src/composables/LayoutManageSideCompounds.ts @@ -457,7 +457,9 @@ async function initializeReactionSideCompounds(subgraphNetwork:SubgraphNetwork,i throw error; } }else if (!network.nodes[idReaction]){ - throw new Error("Reaction not found"); + throw new Error("Reaction not found in network"); + }else{ + throw new Error("No side compounds for reaction"); } } @@ -554,8 +556,8 @@ async function addSideCompoundsIntervals(reaction: Reaction):Promise<Reaction> { if (!reaction.intervalsAvailables || reaction.intervalsAvailables.length === 0) { reaction.intervalsAvailables =[{ typeInterval: 0, - reactant: null, - product: null, + reactant: undefined, + product: undefined, }]; } @@ -628,6 +630,9 @@ function addInterval(reaction:Reaction,id1:string,type1:MetaboliteType,id2:strin */ async function biggestInterval(reaction: Reaction): Promise<{interval:ReactionInterval,size:number}> { try { + if (!reaction.intervalsAvailables) { + throw new Error("intervalsAvailables does not exist"); + } const intervals = reaction.intervalsAvailables; if (intervals.length === 0) { throw new Error("Empty intervals"); @@ -661,6 +666,7 @@ async function biggestInterval(reaction: Reaction): Promise<{interval:ReactionIn function sizeInterval(reaction:Reaction,intervalIndex:number):number{ const angles=reaction.metabolitesAngles; if (angles===undefined) throw new Error("No angles"); + if (!reaction.intervalsAvailables) throw new Error("No intervalsAvailables"); const interval=reaction.intervalsAvailables[intervalIndex]; if (interval===undefined) throw new Error("No interval"); // if reactant or product null : return 2*PI @@ -678,7 +684,7 @@ function sizeInterval(reaction:Reaction,intervalIndex:number):number{ //______________2.3 Stamp motif : find spacing -async function findSpacingSideCompounds(reaction:Reaction,sizeInterval:number):Promise<{reactant:number,product:number}>{ +async function findSpacingSideCompounds(reaction:Reaction,sizeInterval:number):Promise<{reactant:number|undefined,product:number|undefined}>{ const reactantNumber=reaction.sideCompoundsReactants.length; const productNumber=reaction.sideCompoundsProducts.length; return { @@ -700,18 +706,19 @@ async function findSpacingSideCompounds(reaction:Reaction,sizeInterval:number):P * @returns The updated subgraph network with the calculated coordinates for the side compounds. */ async function giveCoordAllSideCompounds(subgraphNetwork:SubgraphNetwork,reaction:Reaction,factorLength:number=1/2):Promise<SubgraphNetwork>{ - const distance=calculateDistance(subgraphNetwork, factorLength); - const sideCompounds=subgraphNetwork.sideCompounds[reaction.id]; - const reactionCoord=subgraphNetwork.network.value.nodes[reaction.id]; - // Reactants Placement - if (sideCompounds.reactants && sideCompounds.reactants.length>0){ - await placeSideCompounds(sideCompounds.reactants, reaction, reactionCoord, distance, true); - } - // Products Placement - if (sideCompounds.products && sideCompounds.products.length>0){ - await placeSideCompounds(sideCompounds.products, reaction, reactionCoord, distance, false); + if (subgraphNetwork.sideCompounds){ + const distance=calculateDistance(subgraphNetwork, factorLength); + const sideCompounds=subgraphNetwork.sideCompounds[reaction.id]; + const reactionCoord=subgraphNetwork.network.value.nodes[reaction.id]; + // Reactants Placement + if (sideCompounds.reactants && sideCompounds.reactants.length>0){ + await placeSideCompounds(sideCompounds.reactants, reaction, reactionCoord, distance, true); + } + // Products Placement + if (sideCompounds.products && sideCompounds.products.length>0){ + await placeSideCompounds(sideCompounds.products, reaction, reactionCoord, distance, false); + } } - return subgraphNetwork; } @@ -726,7 +733,7 @@ async function giveCoordAllSideCompounds(subgraphNetwork:SubgraphNetwork,reactio */ function calculateDistance(subgraphNetwork: SubgraphNetwork, factorLength: number): number { let baseLengthPixel: number; - if (subgraphNetwork.stats.minEdgeLengthPixel) { + if (subgraphNetwork.stats && subgraphNetwork.stats.minEdgeLengthPixel) { baseLengthPixel = subgraphNetwork.stats.minEdgeLengthPixel; } else { console.error("stats minEdgeLengthPixel not found, use default value 1 inch"); @@ -746,20 +753,24 @@ function calculateDistance(subgraphNetwork: SubgraphNetwork, factorLength: numbe * @param placeReactants - A boolean indicating whether to place reactants or products. */ async function placeSideCompounds(sideCompounds: Array<Node>, reaction: Reaction, reactionCoord: Coordinate, distance: number, placeReactants: boolean): Promise<void> { - const startSideCompound = placeReactants ? reaction.intervalsAvailables[0].reactant : reaction.intervalsAvailables[0].product; + if (!reaction.intervalsAvailables || reaction.intervalsAvailables.length === 0){ + throw new Error("No intervals available"); + } + const interval = reaction.intervalsAvailables[0]; + const startSideCompound = placeReactants ? interval.reactant : interval.product; if (!startSideCompound) { console.error("No start side compound found"); return; } let startAngle = startSideCompound ? reaction.metabolitesAngles[startSideCompound].angle : 0; const angleSpacing = placeReactants ? reaction.angleSpacingReactant : reaction.angleSpacingProduct; - if (!isFinite(angleSpacing)) { + if (! angleSpacing! || isFinite(angleSpacing)) { console.error("No angle spacing found"); return; } sideCompounds.forEach((sideCompoundNode, i) => { - const direction = determineDirection(reaction.intervalsAvailables[0].typeInterval, placeReactants); + const direction = determineDirection(interval.typeInterval, placeReactants); const angle = startAngle + direction * (i + 1) * angleSpacing; sideCompoundNode = giveCoordSideCompound(sideCompoundNode, angle, reactionCoord, distance); }); @@ -811,15 +822,17 @@ function giveCoordSideCompound(sideCompound:Node,angle:number,center:{x:number,y * @returns void */ function insertAllSideCompoundsInNetwork(subgraphNetwork:SubgraphNetwork,reaction:Reaction):void{ + if(subgraphNetwork.sideCompounds && subgraphNetwork.sideCompounds[reaction.id]){ const sideCompounds=subgraphNetwork.sideCompounds[reaction.id]; // Reactants - Object.keys(reaction.sideCompoundsReactants).forEach((reactant)=>{ - insertSideCompoundInNetwork(subgraphNetwork,reaction.id,sideCompounds.reactants[reactant],MetaboliteType.REACTANT); + sideCompounds.reactants.forEach((reactant)=>{ + insertSideCompoundInNetwork(subgraphNetwork,reaction.id,reactant,MetaboliteType.REACTANT); }); // Products - Object.keys(reaction.sideCompoundsProducts).forEach((product)=>{ - insertSideCompoundInNetwork(subgraphNetwork,reaction.id,sideCompounds.products[product],MetaboliteType.PRODUCT); + sideCompounds.products.forEach((product)=>{ + insertSideCompoundInNetwork(subgraphNetwork,reaction.id,product,MetaboliteType.PRODUCT); }); + } } /** diff --git a/src/composables/LayoutReversibleReactions.ts b/src/composables/LayoutReversibleReactions.ts index 21c36c3..5af2aab 100644 --- a/src/composables/LayoutReversibleReactions.ts +++ b/src/composables/LayoutReversibleReactions.ts @@ -307,7 +307,7 @@ export function keepFirstReversibleNode(subgraphNetwork:SubgraphNetwork,nodeOrde if(network.nodes[nodeID].metadataLayout.isReversedVersion){ // the duplicated version is the one keeped, its id have to be renamed by the original id nodeToRename[nodeID]=reversibleNodeID; - }else if (!network.nodes[reversibleNodeID].metadataLayout.isReversedVersion){ + }else if (!network.nodes[reversibleNodeID].metadataLayout || !network.nodes[reversibleNodeID].metadataLayout.isReversedVersion){ throw new Error("One duplication of node lack attribut isReversedVersion"); } diff --git a/src/composables/LayoutSugiyamaForce.ts b/src/composables/LayoutSugiyamaForce.ts index 99147e7..fe2ac67 100644 --- a/src/composables/LayoutSugiyamaForce.ts +++ b/src/composables/LayoutSugiyamaForce.ts @@ -6,17 +6,16 @@ import { SubgraphNetwork } from "../types/SubgraphNetwork"; import { Network } from "@metabohub/viz-core/src/types/Network"; // Composable imports -import { getMeanNodesSizePixel } from "./CalculateSize"; -import { getSepAttributesInches, shiftAllToGetTopLeftCoord } from "./CalculateSize"; -import { networkToCytoscape, networkToDagre, graphVizToDot, networkToViz, networkToDOT } from './ConvertFromNetwork'; -import { changeNetworkFromCytoscape, changeNetworkFromDagre, changeNetworkFromViz } from './ConvertToNetwork'; +import { getSepAttributesInches } from "./CalculateSize"; +import { networkToDOT } from './ConvertFromNetwork'; +import { changeNetworkFromViz } from './ConvertToNetwork'; // General imports -import * as d3 from 'd3'; +//import * as d3 from 'd3'; import { reactive } from "vue"; -import cytoscape from 'cytoscape'; -import fcose from 'cytoscape-fcose'; -import cosebilkent from 'cytoscape-cose-bilkent'; +// import cytoscape from 'cytoscape'; +// import fcose from 'cytoscape-fcose'; +// import cosebilkent from 'cytoscape-cose-bilkent'; import dagre from 'dagrejs'; import { Graph, instance } from "@viz-js/viz"; @@ -43,17 +42,17 @@ import { Graph, instance } from "@viz-js/viz"; * @param graphAttributes for dagre layout (see https://github.com/dagrejs/dagre/wiki) * @param [callbackFunction=() => {}] function to do after the layout is done */ -export function dagreLayout(network: Network,graphAttributes={},callbackFunction = () => {}):void { - - setTimeout(async function() { - let graphDagre = networkToDagre(network,graphAttributes); - dagre.layout(graphDagre); - changeNetworkFromDagre(graphDagre, network).then(() => { - callbackFunction(); - }); - }, 1); +// export function dagreLayout(network: Network,graphAttributes={},callbackFunction = () => {}):void { + +// setTimeout(async function() { +// let graphDagre = networkToDagre(network,graphAttributes); +// dagre.layout(graphDagre); +// changeNetworkFromDagre(graphDagre, network).then(() => { +// callbackFunction(); +// }); +// }, 1); -} +// } /** @@ -92,46 +91,46 @@ export async function vizLayout(subgraphNetwork:SubgraphNetwork,assignRank:boole * @param shiftCoord - Optional. Specifies whether to shift the coordinates of the nodes to the top-left corner. Defaults to false. * @returns A Promise that resolves to the updated network after applying the layout. */ -export async function forceLayout(network: Network, networkStyle:GraphStyleProperties, shiftCoord: boolean = false): Promise<Network> { - - //cytoscape.use(fcose); - cytoscape.use(cosebilkent); - - const size=await getMeanNodesSizePixel(Object.values(network.nodes), networkStyle,false); - const edgeFactor=3; - const edgeLength = Math.max(size.height, size.width) * edgeFactor; - - const layout ={ - name:"cose-bilkent", //fcose - animate: false, - randomize: false, - idealEdgeLength: edgeLength, - nodeRepulsion: 70000, // high number if randomize = false - gravity : 0.001, - numIter: 3000 - - } - - let cyto = networkToCytoscape(network,true); - - await new Promise<void>((resolve) => { - cyto.ready(function () { - setTimeout(function () { - cyto.elements().layout(layout).run(); - resolve(); - }, 5000); - }); - }); +// export async function forceLayout(network: Network, networkStyle:GraphStyleProperties, shiftCoord: boolean = false): Promise<Network> { - if (shiftCoord) { - shiftAllToGetTopLeftCoord(network, networkStyle); - } +// //cytoscape.use(fcose); +// cytoscape.use(cosebilkent); - const json = cyto.json(); - changeNetworkFromCytoscape(json, network); +// const size=await getMeanNodesSizePixel(Object.values(network.nodes), networkStyle,false); +// const edgeFactor=3; +// const edgeLength = Math.max(size.height, size.width) * edgeFactor; - return network; -} +// const layout ={ +// name:"cose-bilkent", //fcose +// animate: false, +// randomize: false, +// idealEdgeLength: edgeLength, +// nodeRepulsion: 70000, // high number if randomize = false +// gravity : 0.001, +// numIter: 3000 + +// } + +// let cyto = networkToCytoscape(network,true); + +// await new Promise<void>((resolve) => { +// cyto.ready(function () { +// setTimeout(function () { +// cyto.elements().layout(layout).run(); +// resolve(); +// }, 5000); +// }); +// }); + +// if (shiftCoord) { +// shiftAllToGetTopLeftCoord(network, networkStyle); +// } + +// const json = cyto.json(); +// changeNetworkFromCytoscape(json, network); + +// return network; +// } diff --git a/src/types/Reaction.ts b/src/types/Reaction.ts index 481365f..cf90a30 100644 --- a/src/types/Reaction.ts +++ b/src/types/Reaction.ts @@ -29,6 +29,6 @@ export interface ReactionInterval { typeInterval: number; // 0 if reactant then product, 1 if product then reactant, // but if the x-axis is between the two (special case) : it is 2 when reactant is the smaller angle, 3 when product is the smaller angle - reactant: string; - product: string; + reactant: string|undefined; + product: string|undefined; } \ No newline at end of file -- GitLab From 77c912db96dfcd7f5aba07967ea0a3ea03fc7f68 Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Thu, 12 Sep 2024 14:54:25 +0200 Subject: [PATCH 10/13] clean subgraph files --- src/composables/ConvertFromNetwork.ts | 8 +- src/composables/LayoutFindCycle.ts | 4 +- src/composables/LayoutMainChain.ts | 4 +- src/composables/LayoutManageSideCompounds.ts | 24 +- src/composables/LayoutReversibleReactions.ts | 9 +- src/composables/LayoutSugiyamaForce.ts | 1 - src/composables/MetricsApplication.ts | 652 +++++++-------- src/composables/MetricsCalculation.ts | 756 +++++++++--------- src/composables/SBMLtoJSON.ts | 302 +++---- ...twork.ts => SubgraphForSubgraphNetwork.ts} | 148 ++-- src/composables/SubgraphForViz.ts | 128 +++ src/composables/countIsolatedNodes.ts | 36 - src/composables/importNetwork.ts | 156 ++-- src/composables/useSubgraphs.ts | 207 ----- 14 files changed, 1152 insertions(+), 1283 deletions(-) rename src/composables/{UseSubgraphNetwork.ts => SubgraphForSubgraphNetwork.ts} (52%) create mode 100644 src/composables/SubgraphForViz.ts delete mode 100644 src/composables/countIsolatedNodes.ts delete mode 100644 src/composables/useSubgraphs.ts diff --git a/src/composables/ConvertFromNetwork.ts b/src/composables/ConvertFromNetwork.ts index cf4cc22..af84107 100644 --- a/src/composables/ConvertFromNetwork.ts +++ b/src/composables/ConvertFromNetwork.ts @@ -7,7 +7,7 @@ import { LinkLayout, NetworkLayout, NodeLayout } from '../types/NetworkLayout'; import { Ordering } from '../types/EnumArgs'; // Composable imports -import { addMainChainClusterViz,addClusterDot } from './useSubgraphs'; +import { addMainChainForViz,subgraphDot } from './SubgraphForViz'; import { getSizeNodePixel, pixelsToInches } from './CalculateSize'; import { inCycle } from './GetSetAttributsNodes'; import { cycleMetanodeLink, sortLinksWithAllGroupCycle } from './CalculateRelationCycle'; @@ -322,7 +322,7 @@ export function networkToViz(subgraphNetwork:SubgraphNetwork,cycle:boolean=true, const mainChain = subgraphNetwork[TypeSubgraph.MAIN_CHAIN]; Object.keys(mainChain).sort((a, b) => mainChain[b].nodes.length - mainChain[a].nodes.length) // sort depending on size : bigger first .forEach((nameMainChain) => { - graphViz=addMainChainClusterViz(graphViz,nameMainChain,subgraphNetwork,cycle); + graphViz=addMainChainForViz(graphViz,nameMainChain,subgraphNetwork,cycle); }); } @@ -418,7 +418,7 @@ export function graphVizToDot(vizGraph:Graph, subgraphFirst:boolean=true):string // clusters if (vizGraph.subgraphs) { vizGraph.subgraphs.forEach((subgraph) => { - dotString+=addClusterDot(subgraph as SubgraphViz); + dotString+=subgraphDot(subgraph as SubgraphViz); }); } @@ -455,7 +455,7 @@ export function graphVizToDot(vizGraph:Graph, subgraphFirst:boolean=true):string // clusters if (vizGraph.subgraphs) { vizGraph.subgraphs.forEach((subgraph) => { - dotString+=addClusterDot(subgraph as SubgraphViz); + dotString+=subgraphDot(subgraph as SubgraphViz); }); } } diff --git a/src/composables/LayoutFindCycle.ts b/src/composables/LayoutFindCycle.ts index 52677f0..cde82c8 100644 --- a/src/composables/LayoutFindCycle.ts +++ b/src/composables/LayoutFindCycle.ts @@ -1,6 +1,6 @@ import { SubgraphNetwork } from "../types/SubgraphNetwork"; import { Network } from "@metabohub/viz-core/src/types/Network"; -import { addNewSubgraph, createSubgraph, updateNodeMetadataSubgraph } from "./UseSubgraphNetwork"; +import { addSubgraphToNetwork, createSubgraph, updateNodeMetadataSubgraph } from "./SubgraphForSubgraphNetwork"; import { TypeSubgraph } from "../types/Subgraph"; import { keepFirstReversibleNode, renameAllIDNode } from "./LayoutReversibleReactions"; @@ -52,7 +52,7 @@ export function addDirectedCycleToSubgraphNetwork(subgraphNetwork:SubgraphNetwor } const subgraph=createSubgraph(cycle[0],cycle[1],[],TypeSubgraph.CYCLE); - subgraphNetwork=addNewSubgraph(subgraphNetwork,subgraph,TypeSubgraph.CYCLE); + subgraphNetwork=addSubgraphToNetwork(subgraphNetwork,subgraph,TypeSubgraph.CYCLE); // if combined cycle (with cycle i) : that is, if there are more than one common nodes if (commonNodes.length > 1) { diff --git a/src/composables/LayoutMainChain.ts b/src/composables/LayoutMainChain.ts index 4ffa31c..36950fa 100644 --- a/src/composables/LayoutMainChain.ts +++ b/src/composables/LayoutMainChain.ts @@ -6,7 +6,7 @@ import { getStartNodes } from "./CalculateStartNodes"; import { Network } from "@metabohub/viz-core/src/types/Network"; import { BFS } from "./AlgorithmBFS"; import {TypeSubgraph, type Subgraph} from "../types/Subgraph"; -import { addNewSubgraph, addNodeToSubgraph, createSubgraph, updateNodeMetadataSubgraph } from './UseSubgraphNetwork'; +import { addSubgraphToNetwork, createSubgraph, updateNodeMetadataSubgraph } from './SubgraphForSubgraphNetwork'; /** @@ -97,7 +97,7 @@ export function addMiniBranchToMainChain(subgraphNetwork:SubgraphNetwork):Subgra // add the nodes to a subgraph associated with the main chain if (nodesToAdd.length>0){ const subgraph=createSubgraph("minibranch_"+mainChainID,nodesToAdd,[],TypeSubgraph.SECONDARY_CHAIN,{name:mainChainID,type:TypeSubgraph.MAIN_CHAIN}); - subgraphNetwork=addNewSubgraph(subgraphNetwork,subgraph,TypeSubgraph.SECONDARY_CHAIN); + subgraphNetwork=addSubgraphToNetwork(subgraphNetwork,subgraph,TypeSubgraph.SECONDARY_CHAIN); } }); return subgraphNetwork; diff --git a/src/composables/LayoutManageSideCompounds.ts b/src/composables/LayoutManageSideCompounds.ts index 186395c..908c587 100644 --- a/src/composables/LayoutManageSideCompounds.ts +++ b/src/composables/LayoutManageSideCompounds.ts @@ -8,7 +8,6 @@ import { Coordinate } from "../types/CoordinatesSize"; import { GraphStyleProperties } from "@metabohub/viz-core/src/types/GraphStyleProperties"; // Composable imports -import { getContentFromURL } from "./importNetwork"; import { removeAllSelectedNodes , duplicateAllNodesByAttribut} from "@metabohub/viz-core"; import { getMeanNodesSizePixel, inchesToPixels, minEdgeLength as minEdgeLength, pixelsToInches } from "./CalculateSize"; import { sideCompoundAttribute,isDuplicate, isReaction, isSideCompound, setAsSideCompound } from "./GetSetAttributsNodes"; @@ -40,6 +39,9 @@ import { error } from "console"; * -> getIDSideCompoundsFromFile : * return the list of id of side compounds from a file * + * -> getContentFromURL : + * fetch url to return data + * * ********************************** * @@ -190,6 +192,26 @@ async function getIDSideCompoundsFromFile(pathListSideCompounds:string):Promise< } } +/** + * Fetch url to return data + * @param url URL to fetch + * @returns Return response + */ +export async function getContentFromURL(url: string): Promise<string> { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error('La requête a échoué avec le statut ' + response.status); + } + const content = await response.text(); + return content; + } catch (error) { + console.error('Une erreur s\'est produite lors de la récupération du contenu du fichier :', error); + throw error; + } + } + + /*******************************************************************************************************************************************************/ //___________________________________________________1. Duplicate and remove side compounds__________________________________________________________________________ diff --git a/src/composables/LayoutReversibleReactions.ts b/src/composables/LayoutReversibleReactions.ts index 5af2aab..1bbe991 100644 --- a/src/composables/LayoutReversibleReactions.ts +++ b/src/composables/LayoutReversibleReactions.ts @@ -163,12 +163,11 @@ async function duplicateNodeReactionReversible(networkLayout: NetworkLayout, nod if (!networkLayout.nodes[nodeReaction.id]) { throw new Error("Node not found to set as reversible"); } - - if (networkLayout.nodes[nodeReaction.id].metadataLayout) { - networkLayout.nodes[nodeReaction.id].metadataLayout.reversibleNodeVersion = newId; - }else{ - networkLayout.nodes[nodeReaction.id].metadataLayout = {reversibleNodeVersion:newId}; + let metadataLayout=networkLayout.nodes[nodeReaction.id].metadataLayout; + if (!metadataLayout) { + metadataLayout={}; } + metadataLayout.reversibleNodeVersion = newId; resolve(newReactionNode); } catch (error) { diff --git a/src/composables/LayoutSugiyamaForce.ts b/src/composables/LayoutSugiyamaForce.ts index fe2ac67..4defdab 100644 --- a/src/composables/LayoutSugiyamaForce.ts +++ b/src/composables/LayoutSugiyamaForce.ts @@ -69,7 +69,6 @@ export async function vizLayout(subgraphNetwork:SubgraphNetwork,assignRank:boole await instance().then( async viz => { // attributes for viz const sep =await getSepAttributesInches(subgraphNetwork.network.value,subgraphNetwork.networkStyle.value,factorLenghtEdge); - console.log('sep',sep); 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); if(printDot) console.log(dot); diff --git a/src/composables/MetricsApplication.ts b/src/composables/MetricsApplication.ts index 6fa8868..7a6b41b 100644 --- a/src/composables/MetricsApplication.ts +++ b/src/composables/MetricsApplication.ts @@ -1,329 +1,329 @@ -import { ref } from "vue"; -import { getContentFromURL, importNetworkFromURL } from "./importNetwork"; -import { Network } from "@metabohub/viz-core/src/types/Network"; -import { GraphStyleProperties } from "@metabohub/viz-core/src/types/GraphStyleProperties"; -import { SubgraphNetwork } from "../types/SubgraphNetwork"; -import { addSideCompoundAttributeFromList, duplicateSideCompound, putDuplicatedSideCompoundAside } from "./LayoutManageSideCompounds"; -import { createStaticForceLayout } from "@metabohub/viz-core"; -import { Parameters,defaultParameters } from "../types/Parameters"; -import { forceLayout, vizLayout } from "./LayoutSugiyamaForce"; -import { Algo, PathType } from "../types/EnumArgs"; -import { countIntersectionEdgeNetwork, countOverlapNodeNetwork, countOverlapNodeEdgeNetwork, countDifferentCoordinatesNodeNetwork, countNodes, countEdges, coefficientOfVariationEdgeLength, analyseDirectorVector } from "./MetricsCalculation"; -import { TypeSubgraph } from "../types/Subgraph"; -import { networkToGDSGraph } from "./ConvertFromNetwork"; -import { allSteps } from "./LayoutMain"; - - -export async function analyseAllJSON(pathListJSON: string,algo:Algo=Algo.DEFAULT,metricGraph:boolean=true): Promise<void> { - const jsonFileString = await getContentFromURL(pathListJSON); - const allJson = jsonFileString.split('\n'); - let resultGraphAllJSON: Array<Array<number>> = []; - let resultLayoutAllJSON: Array<Array<number>> = []; - let nameMetrics:{graph:string[],layout:string[]}= {graph:[],layout:[]}; - let nameFile: string[] = []; - - // which layout to apply - let applyLayout: (subgraph: SubgraphNetwork) => Promise<SubgraphNetwork> =defaultApplyLayout; - switch (algo) { - case Algo.FORCE: - console.log('apply Force'); - applyLayout = applyForceLayout; - console.warn('Use of timeout so exec time is not accurate (no comparison possible)'); - break; - case Algo.VIZ: - console.log('apply Viz'); - applyLayout = applyVizLayout; - break; - case Algo.ALGO: - console.log('applyAlgo : default'); - applyLayout = applyAlgo; - break; - case Algo.ALGO_V0: - console.log('applyAlgo_V0: no main chain'); - applyLayout = applyAlgo_V0; - break; - case Algo.ALGO_V1: - console.log('applyAlgo_V1 : longuest'); - applyLayout = applyAlgo_V1; - break; - case Algo.ALGO_V3: - console.log('applyAlgo_V3 : all'); - applyLayout = applyAlgo_V3; - break; - default: - console.log('no change'); - applyLayout = defaultApplyLayout; - break; - } - let firstJSON=true; - for (const json of allJson) { - console.log(json); - const resultJSON= await analyseJSON(json,metricGraph,applyLayout,false); - if (firstJSON){ - nameMetrics.graph=resultJSON.graph.nameMetrics; - nameMetrics.layout=resultJSON.layout.nameMetrics; - firstJSON=false; - } - if (resultJSON.graph !== undefined){ - resultGraphAllJSON.push(resultJSON.graph.result); - } - if (resultJSON.layout !== undefined){ - nameFile.push(json); - resultLayoutAllJSON.push(resultJSON.layout.result); - } +// import { ref } from "vue"; +// import { getContentFromURL, importNetworkFromURL } from "./importNetwork"; +// import { Network } from "@metabohub/viz-core/src/types/Network"; +// import { GraphStyleProperties } from "@metabohub/viz-core/src/types/GraphStyleProperties"; +// import { SubgraphNetwork } from "../types/SubgraphNetwork"; +// import { addSideCompoundAttributeFromList, duplicateSideCompound, putDuplicatedSideCompoundAside } from "./LayoutManageSideCompounds"; +// import { createStaticForceLayout } from "@metabohub/viz-core"; +// import { Parameters,defaultParameters } from "../types/Parameters"; +// import { forceLayout, vizLayout } from "./LayoutSugiyamaForce"; +// import { Algo, PathType } from "../types/EnumArgs"; +// import { countIntersectionEdgeNetwork, countOverlapNodeNetwork, countOverlapNodeEdgeNetwork, countDifferentCoordinatesNodeNetwork, countNodes, countEdges, coefficientOfVariationEdgeLength, analyseDirectorVector } from "./MetricsCalculation"; +// import { TypeSubgraph } from "../types/Subgraph"; +// import { networkToGDSGraph } from "./ConvertFromNetwork"; +// import { allSteps } from "./LayoutMain"; + + +// export async function analyseAllJSON(pathListJSON: string,algo:Algo=Algo.DEFAULT,metricGraph:boolean=true): Promise<void> { +// const jsonFileString = await getContentFromURL(pathListJSON); +// const allJson = jsonFileString.split('\n'); +// let resultGraphAllJSON: Array<Array<number>> = []; +// let resultLayoutAllJSON: Array<Array<number>> = []; +// let nameMetrics:{graph:string[],layout:string[]}= {graph:[],layout:[]}; +// let nameFile: string[] = []; + +// // which layout to apply +// let applyLayout: (subgraph: SubgraphNetwork) => Promise<SubgraphNetwork> =defaultApplyLayout; +// switch (algo) { +// case Algo.FORCE: +// console.log('apply Force'); +// applyLayout = applyForceLayout; +// console.warn('Use of timeout so exec time is not accurate (no comparison possible)'); +// break; +// case Algo.VIZ: +// console.log('apply Viz'); +// applyLayout = applyVizLayout; +// break; +// case Algo.ALGO: +// console.log('applyAlgo : default'); +// applyLayout = applyAlgo; +// break; +// case Algo.ALGO_V0: +// console.log('applyAlgo_V0: no main chain'); +// applyLayout = applyAlgo_V0; +// break; +// case Algo.ALGO_V1: +// console.log('applyAlgo_V1 : longuest'); +// applyLayout = applyAlgo_V1; +// break; +// case Algo.ALGO_V3: +// console.log('applyAlgo_V3 : all'); +// applyLayout = applyAlgo_V3; +// break; +// default: +// console.log('no change'); +// applyLayout = defaultApplyLayout; +// break; +// } +// let firstJSON=true; +// for (const json of allJson) { +// console.log(json); +// const resultJSON= await analyseJSON(json,metricGraph,applyLayout,false); +// if (firstJSON){ +// nameMetrics.graph=resultJSON.graph.nameMetrics; +// nameMetrics.layout=resultJSON.layout.nameMetrics; +// firstJSON=false; +// } +// if (resultJSON.graph !== undefined){ +// resultGraphAllJSON.push(resultJSON.graph.result); +// } +// if (resultJSON.layout !== undefined){ +// nameFile.push(json); +// resultLayoutAllJSON.push(resultJSON.layout.result); +// } - } - - if (metricGraph){ - print1DArray(nameMetrics.graph); - print2DArray(resultGraphAllJSON); - } - print1DArray(nameMetrics.layout); - print2DArray(resultLayoutAllJSON); - - console.warn("If apply metrics on another layout : refresh the page, else results are the same than last time (idk why)"); - console.warn('Some metrics are calculated without side compounds'); -} - - -async function analyseJSON(json: string, metricGraph:boolean=true, applyLayout: (subgraph: SubgraphNetwork) => Promise<SubgraphNetwork> =defaultApplyLayout,printColumnName:boolean=true): - Promise<{graph:{nameMetrics:string[],result:number[]},layout:{nameMetrics:string[],result:number[]}} | undefined> { - - // initialize objects - const networkForJSON = ref<Network>({ id: '', nodes: {}, links: [] }); - const networkStyleforJSON = ref<GraphStyleProperties>({ - nodeStyles: {}, - linkStyles: {} - }); - let startTime:number; - let endTime:number; - let subgraphNetwork:SubgraphNetwork; - let resultGraph: {nameMetrics:string[],result:number[]}= {nameMetrics:[],result:[]}; - let resultLayout:{nameMetrics:string[],result:number[]}= {nameMetrics:[],result:[]}; - - // import network from JSON, and process it - try { - await new Promise<void>((resolve, reject) => { - try { - importNetworkFromURL(json, networkForJSON, networkStyleforJSON, () => { - //// Callback function (after network imported) : - - // set style (same for all) - changeNodeStyles(networkStyleforJSON.value); - // create subgraphNetwork object - subgraphNetwork={network:networkForJSON,networkStyle:networkStyleforJSON,attributs:{},mainChains:{}}; - // duplicate side compounds - addSideCompoundAttributeFromList(subgraphNetwork,"/sideCompounds.txt").then( - ()=>{ - duplicateSideCompound(subgraphNetwork); - } - ).then( - ()=>{ - // calculate metrics of graph - if (metricGraph) { - const metricsGraph=applyMetricsGraph(subgraphNetwork.network.value,printColumnName); - resultGraph.result=metricsGraph.metrics - resultGraph.nameMetrics=metricsGraph.nameMetrics; - } - } - ).then( - ()=>{ - startTime = performance.now(); - } - ).then( - async ()=>{ - // apply layout - subgraphNetwork=await applyLayout(subgraphNetwork); - } - ).then( - ()=>{ - endTime = performance.now(); - } - ).then( - ()=>{ - // calculate metrics on resulting layout - const metricsLayout=applyMetricsLayout(subgraphNetwork,true,printColumnName); - resultLayout.result= metricsLayout.metrics; - resultLayout.nameMetrics=metricsLayout.nameMetrics; - resolve(); - } - ); +// } + +// if (metricGraph){ +// print1DArray(nameMetrics.graph); +// print2DArray(resultGraphAllJSON); +// } +// print1DArray(nameMetrics.layout); +// print2DArray(resultLayoutAllJSON); + +// console.warn("If apply metrics on another layout : refresh the page, else results are the same than last time (idk why)"); +// console.warn('Some metrics are calculated without side compounds'); +// } + + +// async function analyseJSON(json: string, metricGraph:boolean=true, applyLayout: (subgraph: SubgraphNetwork) => Promise<SubgraphNetwork> =defaultApplyLayout,printColumnName:boolean=true): +// Promise<{graph:{nameMetrics:string[],result:number[]},layout:{nameMetrics:string[],result:number[]}} | undefined> { + +// // initialize objects +// const networkForJSON = ref<Network>({ id: '', nodes: {}, links: [] }); +// const networkStyleforJSON = ref<GraphStyleProperties>({ +// nodeStyles: {}, +// linkStyles: {} +// }); +// let startTime:number; +// let endTime:number; +// let subgraphNetwork:SubgraphNetwork; +// let resultGraph: {nameMetrics:string[],result:number[]}= {nameMetrics:[],result:[]}; +// let resultLayout:{nameMetrics:string[],result:number[]}= {nameMetrics:[],result:[]}; + +// // import network from JSON, and process it +// try { +// await new Promise<void>((resolve, reject) => { +// try { +// importNetworkFromURL(json, networkForJSON, networkStyleforJSON, () => { +// //// Callback function (after network imported) : + +// // set style (same for all) +// changeNodeStyles(networkStyleforJSON.value); +// // create subgraphNetwork object +// subgraphNetwork={network:networkForJSON,networkStyle:networkStyleforJSON,attributs:{},mainChains:{}}; +// // duplicate side compounds +// addSideCompoundAttributeFromList(subgraphNetwork,"/sideCompounds.txt").then( +// ()=>{ +// duplicateSideCompound(subgraphNetwork); +// } +// ).then( +// ()=>{ +// // calculate metrics of graph +// if (metricGraph) { +// const metricsGraph=applyMetricsGraph(subgraphNetwork.network.value,printColumnName); +// resultGraph.result=metricsGraph.metrics +// resultGraph.nameMetrics=metricsGraph.nameMetrics; +// } +// } +// ).then( +// ()=>{ +// startTime = performance.now(); +// } +// ).then( +// async ()=>{ +// // apply layout +// subgraphNetwork=await applyLayout(subgraphNetwork); +// } +// ).then( +// ()=>{ +// endTime = performance.now(); +// } +// ).then( +// ()=>{ +// // calculate metrics on resulting layout +// const metricsLayout=applyMetricsLayout(subgraphNetwork,true,printColumnName); +// resultLayout.result= metricsLayout.metrics; +// resultLayout.nameMetrics=metricsLayout.nameMetrics; +// resolve(); +// } +// ); - }); - } catch (error) { - reject(error); - } - }); - } catch (error) { - console.error("error file : " + json + "\n" + error); - return undefined; - } - // add execution time of layout only (not duplication side compounds) - const executionTime = parseFloat((endTime - startTime).toFixed(3)); - if (executionTime) { - resultLayout.result.push(executionTime); - resultLayout.nameMetrics.push('execution time (ms)'); - } +// }); +// } catch (error) { +// reject(error); +// } +// }); +// } catch (error) { +// console.error("error file : " + json + "\n" + error); +// return undefined; +// } +// // add execution time of layout only (not duplication side compounds) +// const executionTime = parseFloat((endTime - startTime).toFixed(3)); +// if (executionTime) { +// resultLayout.result.push(executionTime); +// resultLayout.nameMetrics.push('execution time (ms)'); +// } - return {graph:resultGraph,layout:resultLayout}; -} - - -function changeNodeStyles(networkStyle:GraphStyleProperties):void{ - networkStyle.nodeStyles = { - metabolite: { - width: 25, - height: 25, - fill: '#FFFFFF', - shape: 'circle' - }, - sideCompound: { - width: 12, - height: 12, - fill: '#f0e3e0', - shape: 'circle' - }, - reaction: { - width: 15, - height: 15, - fill: "grey", - shape: 'rect' - }, - // reversible : { - // fill : "green", - // shape:"inverseTriangle" - // }, - // reversibleVersion:{ - // fill:"red", - // shape: "triangle" - // } - - } -} - -function print1DArray(data: Array<string|number|boolean>): void { - const stringData = data.join(','); - console.log(stringData); -} - -function print2DArray(data: Array<Array<string|number|boolean>>): void { - const stringData = data.map(row => row.join(',')).join('\n'); - console.log(stringData); -} - - - -const defaultApplyLayout = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { - return subgraphNetwork; -}; - -const applyForceLayout = (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { - const network=subgraphNetwork.network.value; - const styleNetwork= subgraphNetwork.networkStyle.value; - const subgraphNetworkPromise = new Promise<SubgraphNetwork>(async (resolve, reject) => { - try { - await forceLayout(network, styleNetwork, false); - resolve(subgraphNetwork); - } catch (error) { - reject(error); - } - }); - return subgraphNetworkPromise; -}; - -const applyVizLayout = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { - let parameters: Parameters = defaultParameters; - const subgraphNetworkPromise = new Promise<SubgraphNetwork>((resolve, reject) => { - resolve(vizLayout(subgraphNetwork, false, false, parameters.addNodes, parameters.groupOrCluster, false, false, parameters.dpi, parameters.numberNodeOnEdge)) - }) - return subgraphNetworkPromise; -}; - -const applyAlgo = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { - let parameters: Parameters=defaultParameters; - const subgraphNetworkPromise = new Promise<SubgraphNetwork>((resolve, reject) => { - resolve(allSteps(subgraphNetwork,parameters,false)); - }) - return subgraphNetworkPromise; -}; - -const applyAlgo_V0 = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { - let parameters: Parameters=defaultParameters; - parameters.doMainChain=false; - const subgraphNetworkPromise = new Promise<SubgraphNetwork>((resolve, reject) => { - resolve(allSteps(subgraphNetwork,parameters,false)); - }) - return subgraphNetworkPromise; -}; - -const applyAlgo_V1 = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { - let parameters: Parameters=defaultParameters; - parameters.pathType=PathType.LONGEST; - const subgraphNetworkPromise = new Promise<SubgraphNetwork>((resolve, reject) => { - resolve(allSteps(subgraphNetwork,parameters,false)); - }) - return subgraphNetworkPromise; -}; - -const applyAlgo_V3 = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { - let parameters: Parameters=defaultParameters; - parameters.pathType=PathType.ALL; - const subgraphNetworkPromise = new Promise<SubgraphNetwork>((resolve, reject) => { - resolve(allSteps(subgraphNetwork,parameters,false)); - }) - return subgraphNetworkPromise; -}; - - - - -export function applyMetricsGraph(network: Network,printColumnName:boolean=true): {nameMetrics:string[],metrics:number[]} { - const networkGDS=networkToGDSGraph(network); - const result: number[]=[]; - - const nameColumnGraph: string[] = ['nodes', 'node not side compound','edges','edge not side compound', 'hasDirectedCycle' ]; - if (printColumnName) print1DArray(nameColumnGraph); - - // number of nodes - result.push(countNodes(network,true)); - // number of nodes not side compounds - result.push(countNodes(network,false)); - // number of edges - result.push(countEdges(network,true)); - // number of edges not side compounds - result.push(countEdges(network,false)); - // has directed cycle - result.push(Number(networkGDS.hasCycle())); - - return {nameMetrics:nameColumnGraph,metrics:result}; -} - -export function applyMetricsLayout(subgraphNetwork: SubgraphNetwork, coordAreCenter:boolean=true, printColumnName:boolean=true): {nameMetrics:string[],metrics:number[]} { - const network=subgraphNetwork.network.value; - const networkStyle=subgraphNetwork.networkStyle.value; - const networkGDS=networkToGDSGraph(network); - const result: number[]=[]; - - const nameColumnLayout: string[] = ['node overlap', 'edge node overlap', 'different x (not SD)' ,'different y (not SD)','edge intersections','coef var edge length (no SD)','% colineat axis (not SD)', 'coef var vect dir (not SD)']; - if (printColumnName) print1DArray(nameColumnLayout); - - - //number of node overlap - result.push(countOverlapNodeNetwork(network,networkStyle,coordAreCenter)); - // number of edge node overlap - result.push(countOverlapNodeEdgeNetwork(network,networkStyle,coordAreCenter)); - // number of different x and y coordinates (without side compounds) - const countDiffCoord=countDifferentCoordinatesNodeNetwork(network,networkStyle,coordAreCenter,false); - result.push(countDiffCoord.x); - result.push(countDiffCoord.y); - // number of edges intersections - result.push(countIntersectionEdgeNetwork(network,networkStyle,coordAreCenter)); - // variance edge length (without side compounds?) - result.push(coefficientOfVariationEdgeLength(network,networkStyle,coordAreCenter,false)); - // direction edge : % of edge colinear to axis and coef of variaton of angle - const resultDirection=analyseDirectorVector(network,networkStyle,coordAreCenter,true,false); - result.push(resultDirection.colinearAxis) - result.push(resultDirection.coefVariation) - - return {nameMetrics:nameColumnLayout,metrics:result}; -} +// return {graph:resultGraph,layout:resultLayout}; +// } + + +// function changeNodeStyles(networkStyle:GraphStyleProperties):void{ +// networkStyle.nodeStyles = { +// metabolite: { +// width: 25, +// height: 25, +// fill: '#FFFFFF', +// shape: 'circle' +// }, +// sideCompound: { +// width: 12, +// height: 12, +// fill: '#f0e3e0', +// shape: 'circle' +// }, +// reaction: { +// width: 15, +// height: 15, +// fill: "grey", +// shape: 'rect' +// }, +// // reversible : { +// // fill : "green", +// // shape:"inverseTriangle" +// // }, +// // reversibleVersion:{ +// // fill:"red", +// // shape: "triangle" +// // } + +// } +// } + +// function print1DArray(data: Array<string|number|boolean>): void { +// const stringData = data.join(','); +// console.log(stringData); +// } + +// function print2DArray(data: Array<Array<string|number|boolean>>): void { +// const stringData = data.map(row => row.join(',')).join('\n'); +// console.log(stringData); +// } + + + +// const defaultApplyLayout = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { +// return subgraphNetwork; +// }; + +// const applyForceLayout = (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { +// const network=subgraphNetwork.network.value; +// const styleNetwork= subgraphNetwork.networkStyle.value; +// const subgraphNetworkPromise = new Promise<SubgraphNetwork>(async (resolve, reject) => { +// try { +// await forceLayout(network, styleNetwork, false); +// resolve(subgraphNetwork); +// } catch (error) { +// reject(error); +// } +// }); +// return subgraphNetworkPromise; +// }; + +// const applyVizLayout = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { +// let parameters: Parameters = defaultParameters; +// const subgraphNetworkPromise = new Promise<SubgraphNetwork>((resolve, reject) => { +// resolve(vizLayout(subgraphNetwork, false, false, parameters.addNodes, parameters.groupOrCluster, false, false, parameters.dpi, parameters.numberNodeOnEdge)) +// }) +// return subgraphNetworkPromise; +// }; + +// const applyAlgo = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { +// let parameters: Parameters=defaultParameters; +// const subgraphNetworkPromise = new Promise<SubgraphNetwork>((resolve, reject) => { +// resolve(allSteps(subgraphNetwork,parameters,false)); +// }) +// return subgraphNetworkPromise; +// }; + +// const applyAlgo_V0 = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { +// let parameters: Parameters=defaultParameters; +// parameters.doMainChain=false; +// const subgraphNetworkPromise = new Promise<SubgraphNetwork>((resolve, reject) => { +// resolve(allSteps(subgraphNetwork,parameters,false)); +// }) +// return subgraphNetworkPromise; +// }; + +// const applyAlgo_V1 = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { +// let parameters: Parameters=defaultParameters; +// parameters.pathType=PathType.LONGEST; +// const subgraphNetworkPromise = new Promise<SubgraphNetwork>((resolve, reject) => { +// resolve(allSteps(subgraphNetwork,parameters,false)); +// }) +// return subgraphNetworkPromise; +// }; + +// const applyAlgo_V3 = async (subgraphNetwork: SubgraphNetwork): Promise<SubgraphNetwork> => { +// let parameters: Parameters=defaultParameters; +// parameters.pathType=PathType.ALL; +// const subgraphNetworkPromise = new Promise<SubgraphNetwork>((resolve, reject) => { +// resolve(allSteps(subgraphNetwork,parameters,false)); +// }) +// return subgraphNetworkPromise; +// }; + + + + +// export function applyMetricsGraph(network: Network,printColumnName:boolean=true): {nameMetrics:string[],metrics:number[]} { +// const networkGDS=networkToGDSGraph(network); +// const result: number[]=[]; + +// const nameColumnGraph: string[] = ['nodes', 'node not side compound','edges','edge not side compound', 'hasDirectedCycle' ]; +// if (printColumnName) print1DArray(nameColumnGraph); + +// // number of nodes +// result.push(countNodes(network,true)); +// // number of nodes not side compounds +// result.push(countNodes(network,false)); +// // number of edges +// result.push(countEdges(network,true)); +// // number of edges not side compounds +// result.push(countEdges(network,false)); +// // has directed cycle +// result.push(Number(networkGDS.hasCycle())); + +// return {nameMetrics:nameColumnGraph,metrics:result}; +// } + +// export function applyMetricsLayout(subgraphNetwork: SubgraphNetwork, coordAreCenter:boolean=true, printColumnName:boolean=true): {nameMetrics:string[],metrics:number[]} { +// const network=subgraphNetwork.network.value; +// const networkStyle=subgraphNetwork.networkStyle.value; +// const networkGDS=networkToGDSGraph(network); +// const result: number[]=[]; + +// const nameColumnLayout: string[] = ['node overlap', 'edge node overlap', 'different x (not SD)' ,'different y (not SD)','edge intersections','coef var edge length (no SD)','% colineat axis (not SD)', 'coef var vect dir (not SD)']; +// if (printColumnName) print1DArray(nameColumnLayout); + + +// //number of node overlap +// result.push(countOverlapNodeNetwork(network,networkStyle,coordAreCenter)); +// // number of edge node overlap +// result.push(countOverlapNodeEdgeNetwork(network,networkStyle,coordAreCenter)); +// // number of different x and y coordinates (without side compounds) +// const countDiffCoord=countDifferentCoordinatesNodeNetwork(network,networkStyle,coordAreCenter,false); +// result.push(countDiffCoord.x); +// result.push(countDiffCoord.y); +// // number of edges intersections +// result.push(countIntersectionEdgeNetwork(network,networkStyle,coordAreCenter)); +// // variance edge length (without side compounds?) +// result.push(coefficientOfVariationEdgeLength(network,networkStyle,coordAreCenter,false)); +// // direction edge : % of edge colinear to axis and coef of variaton of angle +// const resultDirection=analyseDirectorVector(network,networkStyle,coordAreCenter,true,false); +// result.push(resultDirection.colinearAxis) +// result.push(resultDirection.coefVariation) + +// return {nameMetrics:nameColumnLayout,metrics:result}; +// } diff --git a/src/composables/MetricsCalculation.ts b/src/composables/MetricsCalculation.ts index 5843b71..522a6d3 100644 --- a/src/composables/MetricsCalculation.ts +++ b/src/composables/MetricsCalculation.ts @@ -1,448 +1,448 @@ -import { Link } from "@metabohub/viz-core/src/types/Link"; -import { Network } from "@metabohub/viz-core/src/types/Network"; -import { Node } from "@metabohub/viz-core/src/types/Node"; -import { getTopLeftCoordFromCenter, getSizeNodePixel, getCenterCoordFromTopLeft } from "./CalculateSize"; -import { checkIntersection } from "line-intersect"; -import { Coordinate,Size } from "../types/CoordinatesSize"; -import { GraphStyleProperties } from "@metabohub/viz-core/src/types/GraphStyleProperties"; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//---------------------------------------------------- Utilitary Functions -----------------------------------------------------------// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - -export function getCenterNode(node: Node,networkStyle:GraphStyleProperties,coordAreCenter:boolean=false): Coordinate { - let nodeCenter: {x:number,y:number}; - - if (coordAreCenter) { - nodeCenter={x:node.x,y:node.y}; - }else{ - nodeCenter=getCenterCoordFromTopLeft(node,networkStyle); - } - return nodeCenter; -} - - -function commonNodeBetween2Links(link1: Link,link2: Link): boolean { - if (link1.source==link2.source || link1.source==link2.target || link1.target==link2.source || link1.target==link2.target) { - return true; - }else { - return false; - } -} - -export function countNodes(network: Network, countSideCompound: boolean = true): number { - if (countSideCompound) { - return Object.keys(network.nodes).length; - } else { - let nodes = 0; - Object.keys(network.nodes).forEach((nodeID) => { - const node = network.nodes[nodeID]; - if (!(node.metadata && node.metadata["isSideCompound"])) { - nodes += 1; - } - }); - return nodes; - } -} - -export function countEdges(network: Network, countSideCompound: boolean = true): number { - if (countSideCompound) { - return network.links.length; - } else { - let links = 0; - network.links.forEach(link => { - if (!(link.source.metadata && link.source.metadata["isSideCompound"]) && !(link.target.metadata && link.target.metadata["isSideCompound"])) { - links += 1; - } - }); - return links; - } -} - -function getNormalizedDirectorVector(link: Link, style: GraphStyleProperties, coordAreCenter: boolean = false): Coordinate { - - const sourceCenter = getCenterNode(link.source, style, coordAreCenter); - const targetCenter = getCenterNode(link.target, style, coordAreCenter); - - const dx = targetCenter.x - sourceCenter.x; - const dy = targetCenter.y - sourceCenter.y; - - const length = edgeLength(link, style, coordAreCenter); - - if (length === 0) { - return { x: 0, y: 0 }; // Handle case with zero length - } - - return { - x: parseFloat((dx / length).toFixed(2)), - y: parseFloat((dy / length).toFixed(2)) - }; -} +// import { Link } from "@metabohub/viz-core/src/types/Link"; +// import { Network } from "@metabohub/viz-core/src/types/Network"; +// import { Node } from "@metabohub/viz-core/src/types/Node"; +// import { getTopLeftCoordFromCenter, getSizeNodePixel, getCenterCoordFromTopLeft } from "./CalculateSize"; +// import { checkIntersection } from "line-intersect"; +// import { Coordinate,Size } from "../types/CoordinatesSize"; +// import { GraphStyleProperties } from "@metabohub/viz-core/src/types/GraphStyleProperties"; + +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //---------------------------------------------------- Utilitary Functions -----------------------------------------------------------// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +// export function getCenterNode(node: Node,networkStyle:GraphStyleProperties,coordAreCenter:boolean=false): Coordinate { +// let nodeCenter: {x:number,y:number}; + +// if (coordAreCenter) { +// nodeCenter={x:node.x,y:node.y}; +// }else{ +// nodeCenter=getCenterCoordFromTopLeft(node,networkStyle); +// } +// return nodeCenter; +// } + + +// function commonNodeBetween2Links(link1: Link,link2: Link): boolean { +// if (link1.source==link2.source || link1.source==link2.target || link1.target==link2.source || link1.target==link2.target) { +// return true; +// }else { +// return false; +// } +// } + +// export function countNodes(network: Network, countSideCompound: boolean = true): number { +// if (countSideCompound) { +// return Object.keys(network.nodes).length; +// } else { +// let nodes = 0; +// Object.keys(network.nodes).forEach((nodeID) => { +// const node = network.nodes[nodeID]; +// if (!(node.metadata && node.metadata["isSideCompound"])) { +// nodes += 1; +// } +// }); +// return nodes; +// } +// } + +// export function countEdges(network: Network, countSideCompound: boolean = true): number { +// if (countSideCompound) { +// return network.links.length; +// } else { +// let links = 0; +// network.links.forEach(link => { +// if (!(link.source.metadata && link.source.metadata["isSideCompound"]) && !(link.target.metadata && link.target.metadata["isSideCompound"])) { +// links += 1; +// } +// }); +// return links; +// } +// } + +// function getNormalizedDirectorVector(link: Link, style: GraphStyleProperties, coordAreCenter: boolean = false): Coordinate { + +// const sourceCenter = getCenterNode(link.source, style, coordAreCenter); +// const targetCenter = getCenterNode(link.target, style, coordAreCenter); + +// const dx = targetCenter.x - sourceCenter.x; +// const dy = targetCenter.y - sourceCenter.y; + +// const length = edgeLength(link, style, coordAreCenter); + +// if (length === 0) { +// return { x: 0, y: 0 }; // Handle case with zero length +// } + +// return { +// x: parseFloat((dx / length).toFixed(2)), +// y: parseFloat((dy / length).toFixed(2)) +// }; +// } -function linkOfSideCompound(link:Link):boolean{ - return (link.source.metadata && (link.source.metadata["isSideCompound"]) as boolean) || (link.target.metadata && (link.target.metadata["isSideCompound"]) as boolean); -} - - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//-------------------------------------------------------- Node Metrics --------------------------------------------------------------// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// function linkOfSideCompound(link:Link):boolean{ +// return (link.source.metadata && (link.source.metadata["isSideCompound"]) as boolean) || (link.target.metadata && (link.target.metadata["isSideCompound"]) as boolean); +// } + + +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //-------------------------------------------------------- Node Metrics --------------------------------------------------------------// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////// -// ------------------------- Node overlap +// ///////////////////////////////////////////////////// +// // ------------------------- Node overlap -export function countOverlapNodeNetwork(network: Network,networkStyle:GraphStyleProperties,coordAreCenter:boolean=false): number { - let nb=0; - const nodesID=Object.keys(network.nodes); +// export function countOverlapNodeNetwork(network: Network,networkStyle:GraphStyleProperties,coordAreCenter:boolean=false): number { +// let nb=0; +// const nodesID=Object.keys(network.nodes); - for (let i=0 ; i<nodesID.length ; i++) { - for (let j=i+1 ; j<nodesID.length ; j++) { - // node1 - const node1=network.nodes[nodesID[i]]; - const coordNode1=getCenterNode(node1,networkStyle,coordAreCenter); - const sizeNode1=getSizeNodePixel(node1,networkStyle); - // node2 - const node2=network.nodes[nodesID[j]]; - const coordNode2=getCenterNode(node2,networkStyle,coordAreCenter); - const sizeNode2=getSizeNodePixel(node2,networkStyle); +// for (let i=0 ; i<nodesID.length ; i++) { +// for (let j=i+1 ; j<nodesID.length ; j++) { +// // node1 +// const node1=network.nodes[nodesID[i]]; +// const coordNode1=getCenterNode(node1,networkStyle,coordAreCenter); +// const sizeNode1=getSizeNodePixel(node1,networkStyle); +// // node2 +// const node2=network.nodes[nodesID[j]]; +// const coordNode2=getCenterNode(node2,networkStyle,coordAreCenter); +// const sizeNode2=getSizeNodePixel(node2,networkStyle); - if (nodesOverlap(coordNode1,sizeNode1,coordNode2,sizeNode2)){ - nb+=1; - } - } - } - return nb; -} +// if (nodesOverlap(coordNode1,sizeNode1,coordNode2,sizeNode2)){ +// nb+=1; +// } +// } +// } +// return nb; +// } -function nodesOverlap(coord1: Coordinate, size1: Size, coord2: Coordinate, size2: Size): boolean { +// function nodesOverlap(coord1: Coordinate, size1: Size, coord2: Coordinate, size2: Size): boolean { - // coordinate are center +// // coordinate are center - if ( !size1.width || !size1.height || !size2.width || !size2.height || !coord1.x || !coord1.y || !coord2.x || !coord2.y) { - // Handle null or undefined inputs appropriately - return false; - } +// if ( !size1.width || !size1.height || !size2.width || !size2.height || !coord1.x || !coord1.y || !coord2.x || !coord2.y) { +// // Handle null or undefined inputs appropriately +// return false; +// } - // rectangle 1 - const left1 = coord1.x - size1.width / 2; - const right1 = coord1.x + size1.width / 2; - const top1 = coord1.y - size1.height / 2; - const bottom1 = coord1.y + size1.height / 2; +// // rectangle 1 +// const left1 = coord1.x - size1.width / 2; +// const right1 = coord1.x + size1.width / 2; +// const top1 = coord1.y - size1.height / 2; +// const bottom1 = coord1.y + size1.height / 2; - // rectangle 2 - const left2 = coord2.x - size2.width / 2; - const right2 = coord2.x + size2.width / 2; - const top2 = coord2.y - size2.height / 2; - const bottom2 = coord2.y + size2.height / 2; +// // rectangle 2 +// const left2 = coord2.x - size2.width / 2; +// const right2 = coord2.x + size2.width / 2; +// const top2 = coord2.y - size2.height / 2; +// const bottom2 = coord2.y + size2.height / 2; - // overlap? - const overlapX = left1 < right2 && right1 > left2; - const overlapY = top1 < bottom2 && bottom1 > top2; +// // overlap? +// const overlapX = left1 < right2 && right1 > left2; +// const overlapY = top1 < bottom2 && bottom1 > top2; - return overlapX && overlapY; -} +// return overlapX && overlapY; +// } -///////////////////////////////////////////////////// -// ------------------------- Node on edge +// ///////////////////////////////////////////////////// +// // ------------------------- Node on edge -export function countOverlapNodeEdgeNetwork(network: Network,networkStyle:GraphStyleProperties,coordAreCenter:boolean=false): number { - let nb=0; - const nodesID=Object.keys(network.nodes); - nodesID.forEach( (nodeID) =>{ +// export function countOverlapNodeEdgeNetwork(network: Network,networkStyle:GraphStyleProperties,coordAreCenter:boolean=false): number { +// let nb=0; +// const nodesID=Object.keys(network.nodes); +// nodesID.forEach( (nodeID) =>{ - const node=network.nodes[nodeID]; - const coordNode1=getCenterNode(node,networkStyle,coordAreCenter); - const sizeNode=getSizeNodePixel(node,networkStyle); - nb += countOverlapEdgeForNode(network,networkStyle,nodeID,coordNode1,sizeNode,coordAreCenter); +// const node=network.nodes[nodeID]; +// const coordNode1=getCenterNode(node,networkStyle,coordAreCenter); +// const sizeNode=getSizeNodePixel(node,networkStyle); +// nb += countOverlapEdgeForNode(network,networkStyle,nodeID,coordNode1,sizeNode,coordAreCenter); - }); +// }); - return nb; -} +// return nb; +// } -function countOverlapEdgeForNode(network:Network,networkStyle:GraphStyleProperties,nodeID:string,coordNode:Coordinate,sizeNode:Size,coordAreCenter:boolean=false): number { - let nb=0; +// function countOverlapEdgeForNode(network:Network,networkStyle:GraphStyleProperties,nodeID:string,coordNode:Coordinate,sizeNode:Size,coordAreCenter:boolean=false): number { +// let nb=0; - network.links.forEach(link => { - // if node not linked to the edge : check if it is on the edge - if(!(link.source.id==nodeID || link.target.id==nodeID)){ +// network.links.forEach(link => { +// // if node not linked to the edge : check if it is on the edge +// if(!(link.source.id==nodeID || link.target.id==nodeID)){ - let coordSource=getCenterNode(link.source,networkStyle,coordAreCenter); - let coordTarget=getCenterNode(link.target,networkStyle,coordAreCenter); +// let coordSource=getCenterNode(link.source,networkStyle,coordAreCenter); +// let coordTarget=getCenterNode(link.target,networkStyle,coordAreCenter); - if (nodeEdgeOverlap(coordNode,sizeNode,coordSource,coordTarget)){ - nb+=1; - } - } - }); - return nb; -} +// if (nodeEdgeOverlap(coordNode,sizeNode,coordSource,coordTarget)){ +// nb+=1; +// } +// } +// }); +// return nb; +// } -function nodeEdgeOverlap(centerCoordNode: Coordinate, sizeNode: Size, coordSource: Coordinate, coordTarget: Coordinate): boolean { // CORRIGER !!!!! +// function nodeEdgeOverlap(centerCoordNode: Coordinate, sizeNode: Size, coordSource: Coordinate, coordTarget: Coordinate): boolean { // CORRIGER !!!!! - // Treat the node as a rectangle (coordinates are center of node) - const rect = { - left: centerCoordNode.x - sizeNode.width / 2, - right: centerCoordNode.x + sizeNode.width / 2, - top: centerCoordNode.y - sizeNode.height / 2, - bottom: centerCoordNode.y + sizeNode.height / 2 - }; - - // Check if any of the edge's endpoints is inside the rectangle => same as node overlap (to suppress ?) - const isPointInsideRect = (point: Coordinate) => - point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom; - - if (isPointInsideRect(coordSource) || isPointInsideRect(coordTarget)) { - return true; // One of the endpoints is inside the rectangle - } - - // Check for overlap between the edge and the sides of the rectangle - // Convert the sides of the rectangle into line segments - const rectangleEdges = [ - { start: { x: rect.left, y: rect.top }, end: { x: rect.right, y: rect.top } }, // Top - { start: { x: rect.right, y: rect.top }, end: { x: rect.right, y: rect.bottom } }, // Right - { start: { x: rect.left, y: rect.bottom }, end: { x: rect.right, y: rect.bottom } }, // Bottom - { start: { x: rect.left, y: rect.top }, end: { x: rect.left, y: rect.bottom } } // Left - ]; - - // Use checkIntersection function to check if two line segments intersect - for (const edge of rectangleEdges) { - const result = checkIntersection(edge.start.x,edge.start.y, edge.end.x,edge.end.y, coordSource.x, coordSource.y,coordTarget.x,coordTarget.y); - if (result.type === "intersecting") { - return true; // There is an overlap - } - } - - return false; // No overlap detected -} - - -///////////////////////////////////////////////////// -// ------------------------- Node different coordinates - -export function countDifferentCoordinatesNodeNetwork(network: Network, networkStyle: GraphStyleProperties, coordAreCenter: boolean = false, countSideCompound:boolean=true,roundAt: number = 2): { x: number, y: number } { - let uniqueX = new Set<number>(); - let uniqueY = new Set<number>(); - - Object.keys(network.nodes).forEach((nodeID) => { - const node = network.nodes[nodeID]; - // Do not count side compounds if countSideCompound is false - if (countSideCompound || !(node.metadata && node.metadata["isSideCompound"])) { - const coordNode = getCenterNode(node, networkStyle, coordAreCenter); +// // Treat the node as a rectangle (coordinates are center of node) +// const rect = { +// left: centerCoordNode.x - sizeNode.width / 2, +// right: centerCoordNode.x + sizeNode.width / 2, +// top: centerCoordNode.y - sizeNode.height / 2, +// bottom: centerCoordNode.y + sizeNode.height / 2 +// }; + +// // Check if any of the edge's endpoints is inside the rectangle => same as node overlap (to suppress ?) +// const isPointInsideRect = (point: Coordinate) => +// point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom; + +// if (isPointInsideRect(coordSource) || isPointInsideRect(coordTarget)) { +// return true; // One of the endpoints is inside the rectangle +// } + +// // Check for overlap between the edge and the sides of the rectangle +// // Convert the sides of the rectangle into line segments +// const rectangleEdges = [ +// { start: { x: rect.left, y: rect.top }, end: { x: rect.right, y: rect.top } }, // Top +// { start: { x: rect.right, y: rect.top }, end: { x: rect.right, y: rect.bottom } }, // Right +// { start: { x: rect.left, y: rect.bottom }, end: { x: rect.right, y: rect.bottom } }, // Bottom +// { start: { x: rect.left, y: rect.top }, end: { x: rect.left, y: rect.bottom } } // Left +// ]; + +// // Use checkIntersection function to check if two line segments intersect +// for (const edge of rectangleEdges) { +// const result = checkIntersection(edge.start.x,edge.start.y, edge.end.x,edge.end.y, coordSource.x, coordSource.y,coordTarget.x,coordTarget.y); +// if (result.type === "intersecting") { +// return true; // There is an overlap +// } +// } + +// return false; // No overlap detected +// } + + +// ///////////////////////////////////////////////////// +// // ------------------------- Node different coordinates + +// export function countDifferentCoordinatesNodeNetwork(network: Network, networkStyle: GraphStyleProperties, coordAreCenter: boolean = false, countSideCompound:boolean=true,roundAt: number = 2): { x: number, y: number } { +// let uniqueX = new Set<number>(); +// let uniqueY = new Set<number>(); + +// Object.keys(network.nodes).forEach((nodeID) => { +// const node = network.nodes[nodeID]; +// // Do not count side compounds if countSideCompound is false +// if (countSideCompound || !(node.metadata && node.metadata["isSideCompound"])) { +// const coordNode = getCenterNode(node, networkStyle, coordAreCenter); - // Round the coordinates based on roundAt - const roundedX = parseFloat(coordNode.x.toFixed(roundAt)); - const roundedY = parseFloat(coordNode.y.toFixed(roundAt)); +// // Round the coordinates based on roundAt +// const roundedX = parseFloat(coordNode.x.toFixed(roundAt)); +// const roundedY = parseFloat(coordNode.y.toFixed(roundAt)); - uniqueX.add(roundedX); - uniqueY.add(roundedY); - } - }); - - return { x: uniqueX.size, y: uniqueY.size }; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//-------------------------------------------------------- Edge Metrics --------------------------------------------------------------// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - - -///////////////////////////////////////////////////// -// ------------------------- Edge intersection -// (overlap not taken into account) - - -/** - * Counts how many crossings are in a network - * @param network the network - * @returns the number of crossings - */ -export function countIntersectionEdgeNetwork(network: Network,style:GraphStyleProperties,coordAreCenter:boolean=false): number { - let nb: number = 0; - for (let i=0 ; i<network.links.length ; i++) { - for (let j=i+1 ; j<network.links.length ; j++) { - const link1=network.links[i]; - const link2=network.links[j]; - if (edgesIntersect(link1, link2,style,coordAreCenter)){ - nb++; - } - } - } - return nb; -} - -function edgesIntersect(link1: Link, link2: Link,style:GraphStyleProperties,coordAreCenter:boolean=false): boolean { - - // Case of common node - if (commonNodeBetween2Links(link1,link2)) { - return false; - } - - // Get center of node : where the link is attached - const node1Center=getCenterNode(link1.source,style,coordAreCenter); - const node2Center=getCenterNode(link1.target,style,coordAreCenter); - const node3Center=getCenterNode(link2.source,style,coordAreCenter); - const node4Center=getCenterNode(link2.target,style,coordAreCenter); - - - // Check intersection - const result = checkIntersection(node1Center.x, node1Center.y, node2Center.x, node2Center.y, node3Center.x, node3Center.y, node4Center.x, node4Center.y); - if (result.type == "intersecting") { - return true; - } else { - return false; - } +// uniqueX.add(roundedX); +// uniqueY.add(roundedY); +// } +// }); + +// return { x: uniqueX.size, y: uniqueY.size }; +// } + +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //-------------------------------------------------------- Edge Metrics --------------------------------------------------------------// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + +// ///////////////////////////////////////////////////// +// // ------------------------- Edge intersection +// // (overlap not taken into account) + + +// /** +// * Counts how many crossings are in a network +// * @param network the network +// * @returns the number of crossings +// */ +// export function countIntersectionEdgeNetwork(network: Network,style:GraphStyleProperties,coordAreCenter:boolean=false): number { +// let nb: number = 0; +// for (let i=0 ; i<network.links.length ; i++) { +// for (let j=i+1 ; j<network.links.length ; j++) { +// const link1=network.links[i]; +// const link2=network.links[j]; +// if (edgesIntersect(link1, link2,style,coordAreCenter)){ +// nb++; +// } +// } +// } +// return nb; +// } + +// function edgesIntersect(link1: Link, link2: Link,style:GraphStyleProperties,coordAreCenter:boolean=false): boolean { + +// // Case of common node +// if (commonNodeBetween2Links(link1,link2)) { +// return false; +// } + +// // Get center of node : where the link is attached +// const node1Center=getCenterNode(link1.source,style,coordAreCenter); +// const node2Center=getCenterNode(link1.target,style,coordAreCenter); +// const node3Center=getCenterNode(link2.source,style,coordAreCenter); +// const node4Center=getCenterNode(link2.target,style,coordAreCenter); + + +// // Check intersection +// const result = checkIntersection(node1Center.x, node1Center.y, node2Center.x, node2Center.y, node3Center.x, node3Center.y, node4Center.x, node4Center.y); +// if (result.type == "intersecting") { +// return true; +// } else { +// return false; +// } -} +// } -///////////////////////////////////////////////////// -// ------------------------- Edge length +// ///////////////////////////////////////////////////// +// // ------------------------- Edge length -export function coefficientOfVariationEdgeLength(network: Network,style:GraphStyleProperties,coordAreCenter:boolean=false,includeSideCompounds:boolean=true): number { +// export function coefficientOfVariationEdgeLength(network: Network,style:GraphStyleProperties,coordAreCenter:boolean=false,includeSideCompounds:boolean=true): number { - let links = network.links; +// let links = network.links; - if (!includeSideCompounds) { - links = links.filter(link => - !(link.source.metadata && link.source.metadata["isSideCompound"]) && - !(link.target.metadata && link.target.metadata["isSideCompound"]) - ); - } +// if (!includeSideCompounds) { +// links = links.filter(link => +// !(link.source.metadata && link.source.metadata["isSideCompound"]) && +// !(link.target.metadata && link.target.metadata["isSideCompound"]) +// ); +// } - if (links.length === 0) { - return 0; // Handle case with no edge - } +// if (links.length === 0) { +// return 0; // Handle case with no edge +// } - const lengths = links.map(link => edgeLength(link, style, coordAreCenter)); +// const lengths = links.map(link => edgeLength(link, style, coordAreCenter)); - const mean = lengths.reduce((a, b) => a + b, 0) / lengths.length; - if (mean === 0) { - return 0; // Handle case with no edge lengths - } - const variance = lengths.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / lengths.length; - const stdDeviation = Math.sqrt(variance); - const coefVariation = stdDeviation / Math.abs(mean); - return parseFloat(coefVariation.toFixed(3)); -} - -function edgeLength(link: Link,style:GraphStyleProperties,coordAreCenter:boolean=false): number { - const sourceCenter=getCenterNode(link.source,style,coordAreCenter); - const targetCenter=getCenterNode(link.target,style,coordAreCenter); - - const dx = sourceCenter.x - targetCenter.x; - const dy = sourceCenter.y - targetCenter.y; - return Math.sqrt(dx * dx + dy * dy); -} - - - -///////////////////////////////////////////////////// -// ------------------------- Edge colinear with axis -// => function used with edge direction - -function calculateNormalizedDirectorVectors(links: Link[], style: GraphStyleProperties, coordAreCenter: boolean = false,includeSideCompounds:boolean=true): Coordinate[] { - const vectors: Coordinate[] = []; - - links.forEach(link => { - if (includeSideCompounds || !linkOfSideCompound(link)){ - const normalizedVector = getNormalizedDirectorVector(link, style, coordAreCenter); - vectors.push(normalizedVector); - } +// const mean = lengths.reduce((a, b) => a + b, 0) / lengths.length; +// if (mean === 0) { +// return 0; // Handle case with no edge lengths +// } +// const variance = lengths.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / lengths.length; +// const stdDeviation = Math.sqrt(variance); +// const coefVariation = stdDeviation / Math.abs(mean); +// return parseFloat(coefVariation.toFixed(3)); +// } + +// function edgeLength(link: Link,style:GraphStyleProperties,coordAreCenter:boolean=false): number { +// const sourceCenter=getCenterNode(link.source,style,coordAreCenter); +// const targetCenter=getCenterNode(link.target,style,coordAreCenter); + +// const dx = sourceCenter.x - targetCenter.x; +// const dy = sourceCenter.y - targetCenter.y; +// return Math.sqrt(dx * dx + dy * dy); +// } + + + +// ///////////////////////////////////////////////////// +// // ------------------------- Edge colinear with axis +// // => function used with edge direction + +// function calculateNormalizedDirectorVectors(links: Link[], style: GraphStyleProperties, coordAreCenter: boolean = false,includeSideCompounds:boolean=true): Coordinate[] { +// const vectors: Coordinate[] = []; + +// links.forEach(link => { +// if (includeSideCompounds || !linkOfSideCompound(link)){ +// const normalizedVector = getNormalizedDirectorVector(link, style, coordAreCenter); +// vectors.push(normalizedVector); +// } - }); +// }); - return vectors; -} +// return vectors; +// } -function isColinearAxisNetwork(vector:Coordinate): boolean { - return vector.x === 0 || vector.y === 0; -} +// function isColinearAxisNetwork(vector:Coordinate): boolean { +// return vector.x === 0 || vector.y === 0; +// } -function countEdgeColinearAxisNetwork(vectors:Coordinate[], pourcentage:boolean=false): number { +// function countEdgeColinearAxisNetwork(vectors:Coordinate[], pourcentage:boolean=false): number { - if (vectors.length === 0) { - return 0; - } +// if (vectors.length === 0) { +// return 0; +// } - let count = 0; - vectors.forEach(vector => { - if (isColinearAxisNetwork(vector)) { - count += 1; - } - }); - if (pourcentage) { - return parseFloat((count / vectors.length).toFixed(2)); - } - return count; -} +// let count = 0; +// vectors.forEach(vector => { +// if (isColinearAxisNetwork(vector)) { +// count += 1; +// } +// }); +// if (pourcentage) { +// return parseFloat((count / vectors.length).toFixed(2)); +// } +// return count; +// } -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//-------------------------------------------------------- Domain Metrics --------------------------------------------------------------// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //-------------------------------------------------------- Domain Metrics --------------------------------------------------------------// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// WHEN CLEAN : fonction that said if sidecompound or not +// // WHEN CLEAN : fonction that said if sidecompound or not -///////////////////////////////////////////////////// -// ------------------------- Edge direction +// ///////////////////////////////////////////////////// +// // ------------------------- Edge direction -export function analyseDirectorVector(network: Network, style: GraphStyleProperties, coordAreCenter: boolean = false, pourcentageColinearAxis:boolean=false, includeSideCompounds:boolean=true):{colinearAxis:number,coefVariation:number} { +// export function analyseDirectorVector(network: Network, style: GraphStyleProperties, coordAreCenter: boolean = false, pourcentageColinearAxis:boolean=false, includeSideCompounds:boolean=true):{colinearAxis:number,coefVariation:number} { - const result: { colinearAxis: number, coefVariation: number } = { colinearAxis: undefined, coefVariation: undefined }; +// const result: { colinearAxis: number, coefVariation: number } = { colinearAxis: undefined, coefVariation: undefined }; - let links = network.links; +// let links = network.links; - if (!includeSideCompounds) { - links = links.filter(link => - !(link.source.metadata && link.source.metadata["isSideCompound"]) && - !(link.target.metadata && link.target.metadata["isSideCompound"]) - ); - } +// if (!includeSideCompounds) { +// links = links.filter(link => +// !(link.source.metadata && link.source.metadata["isSideCompound"]) && +// !(link.target.metadata && link.target.metadata["isSideCompound"]) +// ); +// } - // get all normalized director vectors - const vectors = calculateNormalizedDirectorVectors(links, style, coordAreCenter,includeSideCompounds); - if (vectors.length==0) return { colinearAxis: 0, coefVariation: 0 } +// // get all normalized director vectors +// const vectors = calculateNormalizedDirectorVectors(links, style, coordAreCenter,includeSideCompounds); +// if (vectors.length==0) return { colinearAxis: 0, coefVariation: 0 } - // count colinear with axis - result.colinearAxis = countEdgeColinearAxisNetwork(vectors,pourcentageColinearAxis); +// // count colinear with axis +// result.colinearAxis = countEdgeColinearAxisNetwork(vectors,pourcentageColinearAxis); - // coeficient of variation of angle +// // coeficient of variation of angle - // calculate angles of vectors - const angles = vectors.map(vector => Math.atan2(vector.y, vector.x)); +// // calculate angles of vectors +// const angles = vectors.map(vector => Math.atan2(vector.y, vector.x)); - // calculate mean of angles - const mean = angles.reduce((a, b) => a + b, 0) / angles.length; - if (mean === 0) { - result.coefVariation = 0; // Handle case with no angles - return result; - } +// // calculate mean of angles +// const mean = angles.reduce((a, b) => a + b, 0) / angles.length; +// if (mean === 0) { +// result.coefVariation = 0; // Handle case with no angles +// return result; +// } - // calculate variance of angles - const variance = angles.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / angles.length; +// // calculate variance of angles +// const variance = angles.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / angles.length; - // calculate standard deviation - const stdDeviation = Math.sqrt(variance); +// // calculate standard deviation +// const stdDeviation = Math.sqrt(variance); - // calculate coefficient of variation - const coefVariation = stdDeviation / mean; - result.coefVariation = parseFloat(coefVariation.toFixed(2)); +// // calculate coefficient of variation +// const coefVariation = stdDeviation / mean; +// result.coefVariation = parseFloat(coefVariation.toFixed(2)); - return result; +// return result; -} +// } diff --git a/src/composables/SBMLtoJSON.ts b/src/composables/SBMLtoJSON.ts index b57bce0..4a16b9f 100644 --- a/src/composables/SBMLtoJSON.ts +++ b/src/composables/SBMLtoJSON.ts @@ -1,160 +1,160 @@ -import { parseString } from 'xml2js'; -import type { JSONGraphFormat, XMLSpecies, XMLReactions } from '../types/JSONGraphFormat'; +// import { parseString } from 'xml2js'; +// import type { JSONGraphFormat, XMLSpecies, XMLReactions } from '../types/JSONGraphFormat'; -/** - * Return a number between min (inclusive) and max (inclusive) - * @param min - * @param max - * @returns a number - */ -function getRandomInt(min: number, max: number): number { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; -} +// /** +// * Return a number between min (inclusive) and max (inclusive) +// * @param min +// * @param max +// * @returns a number +// */ +// function getRandomInt(min: number, max: number): number { +// min = Math.ceil(min); +// max = Math.floor(max); +// return Math.floor(Math.random() * (max - min + 1)) + min; +// } -/** - * Convert an xml graph into a JSON graph format - * @param sbmlString the xml file as a string - * @returns the graph as a Promise - */ -export async function sbml2json(sbmlString: string): Promise<JSONGraphFormat> { - return new Promise((resolve, reject) => { - parseString(sbmlString, { explicitArray: false }, (err, result) => { - if (err) { - console.log("Error during the parsing of the file"); - console.log(err); - reject(err); - } else { - const model = result.sbml.model; - // graph to return - const graph: JSONGraphFormat = { - graph: { - id: model.$.id, - type: 'metabolic', - metadata: { - style: { - nodeStyles: { - metabolite: { - width: 25, - height: 25, - strokeWidth: 1, - shape: 'circle' - }, - reaction: { - width: 15, - height: 15, - strokeWidth: 0.5, - shape: 'rect', - fill: 'grey' - }, - reversible: { - fill: 'green', - shape: 'inverseTriangle' - }, - reversibleVersion: { - fill: 'red', - shape: 'triangle' - } - } - } - }, - nodes: {}, - edges: [] - } - }; +// /** +// * Convert an xml graph into a JSON graph format +// * @param sbmlString the xml file as a string +// * @returns the graph as a Promise +// */ +// export async function sbml2json(sbmlString: string): Promise<JSONGraphFormat> { +// return new Promise((resolve, reject) => { +// parseString(sbmlString, { explicitArray: false }, (err, result) => { +// if (err) { +// console.log("Error during the parsing of the file"); +// console.log(err); +// reject(err); +// } else { +// const model = result.sbml.model; +// // graph to return +// const graph: JSONGraphFormat = { +// graph: { +// id: model.$.id, +// type: 'metabolic', +// metadata: { +// style: { +// nodeStyles: { +// metabolite: { +// width: 25, +// height: 25, +// strokeWidth: 1, +// shape: 'circle' +// }, +// reaction: { +// width: 15, +// height: 15, +// strokeWidth: 0.5, +// shape: 'rect', +// fill: 'grey' +// }, +// reversible: { +// fill: 'green', +// shape: 'inverseTriangle' +// }, +// reversibleVersion: { +// fill: 'red', +// shape: 'triangle' +// } +// } +// } +// }, +// nodes: {}, +// edges: [] +// } +// }; - // Transform species to nodes - const speciesList = model.listOfSpecies.species; - // add a metabolite node for each species - speciesList.forEach((species: XMLSpecies) => { - graph.graph.nodes[species.$.id] = { - id: species.$.id, - metadata: { - classes: ['metabolite'], - position: { - x: getRandomInt(0, 100), - y: getRandomInt(0, 100) - } - }, - label: species.$.name - }; - }); +// // Transform species to nodes +// const speciesList = model.listOfSpecies.species; +// // add a metabolite node for each species +// speciesList.forEach((species: XMLSpecies) => { +// graph.graph.nodes[species.$.id] = { +// id: species.$.id, +// metadata: { +// classes: ['metabolite'], +// position: { +// x: getRandomInt(0, 100), +// y: getRandomInt(0, 100) +// } +// }, +// label: species.$.name +// }; +// }); - // Transform reactions to nodes and edges - const reactions = model.listOfReactions.reaction; - reactions.forEach((reaction: XMLReactions) => { - const reactionId = reaction.$.id; +// // Transform reactions to nodes and edges +// const reactions = model.listOfReactions.reaction; +// reactions.forEach((reaction: XMLReactions) => { +// const reactionId = reaction.$.id; - let classReversible :string; - const isReversible=reaction.$.reversible; - if (isReversible==="true"){ - classReversible = "reversible"; - }else{ - classReversible = "irreversible"; - } +// let classReversible :string; +// const isReversible=reaction.$.reversible; +// if (isReversible==="true"){ +// classReversible = "reversible"; +// }else{ +// classReversible = "irreversible"; +// } - // get the reactants and products for every reaction - const reactants: string[] = []; - if (reaction.listOfReactants.speciesReference != undefined && (reaction.listOfReactants.speciesReference as Partial<XMLSpecies>[]).length != undefined) { - // type : array - (reaction.listOfReactants.speciesReference as Partial<XMLSpecies>[]).forEach((ref: Partial<XMLSpecies>) => { - reactants.push(ref.$.species); - }); - } else if (reaction.listOfReactants.speciesReference != undefined) { - // type : object - reactants.push((reaction.listOfReactants.speciesReference as Partial<XMLSpecies>).$.species); - } - const products: string[] = []; - if (reaction.listOfProducts.speciesReference != undefined && (reaction.listOfProducts.speciesReference as Partial<XMLSpecies>[]).length != undefined) { - // type : array - (reaction.listOfProducts.speciesReference as Partial<XMLSpecies>[]).forEach((ref: Partial<XMLSpecies>) => { - products.push(ref.$.species); - }); - } else if (reaction.listOfProducts.speciesReference != undefined) { - // type : object - products.push((reaction.listOfProducts.speciesReference as Partial<XMLSpecies>).$.species); - } +// // get the reactants and products for every reaction +// const reactants: string[] = []; +// if (reaction.listOfReactants.speciesReference != undefined && (reaction.listOfReactants.speciesReference as Partial<XMLSpecies>[]).length != undefined) { +// // type : array +// (reaction.listOfReactants.speciesReference as Partial<XMLSpecies>[]).forEach((ref: Partial<XMLSpecies>) => { +// reactants.push(ref.$.species); +// }); +// } else if (reaction.listOfReactants.speciesReference != undefined) { +// // type : object +// reactants.push((reaction.listOfReactants.speciesReference as Partial<XMLSpecies>).$.species); +// } +// const products: string[] = []; +// if (reaction.listOfProducts.speciesReference != undefined && (reaction.listOfProducts.speciesReference as Partial<XMLSpecies>[]).length != undefined) { +// // type : array +// (reaction.listOfProducts.speciesReference as Partial<XMLSpecies>[]).forEach((ref: Partial<XMLSpecies>) => { +// products.push(ref.$.species); +// }); +// } else if (reaction.listOfProducts.speciesReference != undefined) { +// // type : object +// products.push((reaction.listOfProducts.speciesReference as Partial<XMLSpecies>).$.species); +// } - // add the reaction as a node - graph.graph.nodes[reactionId] = { - id: reactionId, - metadata: { - classes: ['reaction',classReversible], - position: { - x: getRandomInt(0, 100), - y: getRandomInt(0, 100) - } - }, - label: reaction.$.name - }; +// // add the reaction as a node +// graph.graph.nodes[reactionId] = { +// id: reactionId, +// metadata: { +// classes: ['reaction',classReversible], +// position: { +// x: getRandomInt(0, 100), +// y: getRandomInt(0, 100) +// } +// }, +// label: reaction.$.name +// }; - // add the edges for the reaction and its reactants and products - reactants.forEach((reactant: string) => { - graph.graph.edges.push({ - id: `${reactant}--${reactionId}`, - source: reactant, - target: reactionId, - metadata: { - classes: [classReversible] - } - }); - }); - products.forEach((product: string) => { - graph.graph.edges.push({ - id: `${reactionId}--${product}`, - source: reactionId, - target: product, - metadata: { - classes: [classReversible] - } - }); - }); - }); +// // add the edges for the reaction and its reactants and products +// reactants.forEach((reactant: string) => { +// graph.graph.edges.push({ +// id: `${reactant}--${reactionId}`, +// source: reactant, +// target: reactionId, +// metadata: { +// classes: [classReversible] +// } +// }); +// }); +// products.forEach((product: string) => { +// graph.graph.edges.push({ +// id: `${reactionId}--${product}`, +// source: reactionId, +// target: product, +// metadata: { +// classes: [classReversible] +// } +// }); +// }); +// }); - // return the graph object - resolve(graph); - } - }); - }); -} +// // return the graph object +// resolve(graph); +// } +// }); +// }); +// } diff --git a/src/composables/UseSubgraphNetwork.ts b/src/composables/SubgraphForSubgraphNetwork.ts similarity index 52% rename from src/composables/UseSubgraphNetwork.ts rename to src/composables/SubgraphForSubgraphNetwork.ts index df5097c..1ad6b23 100644 --- a/src/composables/UseSubgraphNetwork.ts +++ b/src/composables/SubgraphForSubgraphNetwork.ts @@ -1,8 +1,26 @@ +// Type imports import { NetworkLayout } from "../types/NetworkLayout"; import { Subgraph, TypeSubgraph } from "../types/Subgraph"; import { SubgraphNetwork } from "../types/SubgraphNetwork"; -import { Network } from "@metabohub/viz-core/src/types/Network"; -import { Node } from "@metabohub/viz-core/src/types/Node"; + + +/** + * This file contains functions to manage subgraphs in a subgraphNetwork. + * + * -> createSubgraph : + * creates a new subgraph with the specified properties. + * + * -> addSubgraphToNetwork : + * adds a subgraph to the subgraph network. + * + * -> updateNodeMetadataSubgraph : + * updates the metadata of a node in the network by adding a subgraph ID to its list of subgraph. + * + * -> updateParentSubgraphOf : + * updates the parent of a subgraph by adding the subgraph to its list of child subgraph. + */ + + /** @@ -23,7 +41,15 @@ export function createSubgraph(name: string, nodes: Array<string>, classes: Arra }; } -export function addNewSubgraph(subgraphNetwork:SubgraphNetwork,subgraph:Subgraph,type:TypeSubgraph=TypeSubgraph.MAIN_CHAIN): SubgraphNetwork { +/** + * Adds a subgraph to the subgraph network. + * + * @param subgraphNetwork - The subgraph network to add the subgraph to. + * @param subgraph - The subgraph to add. + * @param type - The type of the subgraph. Defaults to TypeSubgraph.MAIN_CHAIN. + * @returns The updated subgraph network. + */ +export function addSubgraphToNetwork(subgraphNetwork:SubgraphNetwork,subgraph:Subgraph,type:TypeSubgraph=TypeSubgraph.MAIN_CHAIN): SubgraphNetwork { if (!subgraphNetwork[type]) subgraphNetwork[type]={}; if(!(subgraph.name in subgraphNetwork[type])){ // adding subgraph to subgraphNetwork @@ -37,97 +63,12 @@ export function addNewSubgraph(subgraphNetwork:SubgraphNetwork,subgraph:Subgraph subgraphNetwork=updateParentSubgraphOf(subgraphNetwork,subgraph); } }else{ - console.error("subgraph already in subgraphNetwork : "+subgraph.name); + throw new Error("subgraph already in subgraphNetwork : "+subgraph.name); } return subgraphNetwork; } -export function updateParentSubgraphOf(subgraphNetwork:SubgraphNetwork,subgraph:Subgraph):SubgraphNetwork{ - if (subgraph.parentSubgraph){ - const nameParent=subgraph.parentSubgraph.name; - const typeParent=subgraph.parentSubgraph.type; - if (nameParent in subgraphNetwork[typeParent]){ - if (!subgraphNetwork[typeParent][nameParent].childrenSubgraphs){ - subgraphNetwork[typeParent][nameParent].childrenSubgraphs=[]; - } - subgraphNetwork[typeParent][nameParent].childrenSubgraphs.push({name:subgraph.name,type:subgraph.type}); - }else{ - console.error("parent subgraph not in subgraphNetwork"); - } - } - return subgraphNetwork; -} - -/** - * Adds a class to the cluster if it doesn't already exist. - * @param subgraph The cluster to which the class will be added. - * @param newClass The class to be added. - * @returns The updated cluster. - */ -export function addClassSubgraph(subgraph: Subgraph, newClass: string): Subgraph { - if (subgraph.classes && !subgraph.classes.includes(newClass)) { - subgraph.classes.push(newClass); - } - return subgraph; -} - -/** - * Removes a node from the cluster. - * @param subgraph The cluster from which the node will be removed. - * @param name The name of the node to be removed. - * @returns The updated cluster. - */ -export function removeNodeSubgraph(subgraph: Subgraph, name: string): Subgraph { - const index = subgraph.nodes.indexOf(name); - if (index !== -1) { - subgraph.nodes.splice(index, 1); - } - return subgraph; -} - -/** - * Removes a class from the cluster. - * @param subgraph The cluster from which the class will be removed. - * @param className The name of the class to be removed. - * @returns The updated cluster. - */ -export function removeClassSubgraph(subgraph: Subgraph, className: string): Subgraph { - if (subgraph.classes) { - const index = subgraph.classes.indexOf(className); - if (index !== -1) { - subgraph.classes.splice(index, 1); - } - } - return subgraph; -} - - -/** - * Adds a node to a subgraph in the subgraph network, and update the metadata of the node (name of the subgraph to wich it belongs) - * - * @param subgraphNetwork - The subgraph network object. - * @param subgraphID - The ID of the subgraph. - * @param nodeID - The ID of the node to be added. - * @param subgraphType - The type of the subgraph (defaults to MAIN_CHAIN). - * @returns The updated subgraph network object. - */ -export function addNodeToSubgraph(subgraphNetwork:SubgraphNetwork,subgraphID:string,nodeID:string,subgraphType: TypeSubgraph = TypeSubgraph.MAIN_CHAIN):SubgraphNetwork{ - const network=subgraphNetwork.network.value; - - if (subgraphID in subgraphNetwork[subgraphType]){ - // if node not already in subgraph : - if (!subgraphNetwork[subgraphType][subgraphID].nodes.includes(nodeID)){ - // add to subgraph - subgraphNetwork[subgraphType][subgraphID].nodes.push(nodeID); - // update metadata of node - updateNodeMetadataSubgraph(network, nodeID, subgraphID,subgraphType); - } - }else{ - console.error("subgraph not in subgraphNetwork"); - } - return subgraphNetwork; -} /** * Updates the metadata of a node in the network by adding a subgraph ID to its list of subgraph. @@ -146,15 +87,38 @@ export function updateNodeMetadataSubgraph(networkLayout: NetworkLayout, nodeID: // if subgraphType is CYCLEGROUP, add the subgraphID to the metadataLayout directly if (subgraphType == TypeSubgraph.CYCLEGROUP){ networkLayout.nodes[nodeID].metadataLayout[subgraphType]=subgraphID; - return; + } else { // if subgraphType is not CYCLEGROUP, add the subgraphID to the metadataLayout[subgraphType] array + let listSubgraphOfNode: Array<string>; if (!(subgraphType in networkLayout.nodes[nodeID].metadataLayout)){ networkLayout.nodes[nodeID].metadataLayout[subgraphType]=[]; } - const listSubgraphOfNode=networkLayout.nodes[nodeID].metadataLayout[subgraphType]; + listSubgraphOfNode=networkLayout.nodes[nodeID].metadataLayout[subgraphType] as Array<string>; if (!listSubgraphOfNode.includes(subgraphID)){ listSubgraphOfNode.push(subgraphID); } } -} \ No newline at end of file +} + +/** + * Updates the parent of a subgraph by adding the subgraph to its list of child subgraph. + * @param subgraphNetwork + * @param subgraph + * @returns subgraphNetwork updated + */ +export function updateParentSubgraphOf(subgraphNetwork:SubgraphNetwork,subgraph:Subgraph):SubgraphNetwork{ + if (subgraph.parentSubgraph){ + const nameParent=subgraph.parentSubgraph.name; + const typeParent=subgraph.parentSubgraph.type; + if (subgraphNetwork[typeParent] && nameParent in subgraphNetwork[typeParent]){ + if (!subgraphNetwork[typeParent][nameParent].childrenSubgraphs){ + subgraphNetwork[typeParent][nameParent].childrenSubgraphs=[]; + } + subgraphNetwork[typeParent][nameParent].childrenSubgraphs.push({name:subgraph.name,type:subgraph.type as TypeSubgraph}); + }else{ + throw new Error("parent subgraph not in subgraphNetwork"); + } + } + return subgraphNetwork; +} diff --git a/src/composables/SubgraphForViz.ts b/src/composables/SubgraphForViz.ts new file mode 100644 index 0000000..797ccd5 --- /dev/null +++ b/src/composables/SubgraphForViz.ts @@ -0,0 +1,128 @@ +// Type imports +import { TypeSubgraph } from "../types/Subgraph"; +import { SubgraphNetwork } from "../types/SubgraphNetwork"; + +// General imports +import { Graph } from "@viz-js/viz"; + +/** + * This file contains functions to manage subgraphs for viz graph. + * + * -> addMainChainForViz : + * Adds a main chain to the viz graph. + * + * -> changeCycleMetanodes : + * Changes list of nodes to replace nodes in cycle by the metanode. + * + * -> subgraphDot : + * Create a subgraph dot representation for viz. + */ + + +/** + * Adds a main chain to the viz graph. + * + * @param vizGraph - The viz graph to add the main chain to. + * @param nameMainChain - The name of the main chain. + * @param subgraphNetwork - The subgraph network containing the main chain. + * @param cycle - Optional. Specifies whether to apply cycle changes to the nodes. Defaults to true. + * @param isCluster - Optional. Specifies whether the main chain should be treated as a cluster. Defaults to true. + * @returns The updated visualization graph. + * @throws Error if the main chain is not found in the subgraph network. + */ +export function addMainChainForViz(vizGraph: Graph, nameMainChain: string, subgraphNetwork:SubgraphNetwork,cycle:boolean=true,isCluster:boolean=true): Graph { + if ( !subgraphNetwork[TypeSubgraph.MAIN_CHAIN] || !subgraphNetwork[TypeSubgraph.MAIN_CHAIN][nameMainChain]) throw new Error("Main chain not found in subgraphNetwork"); + + // get values from cluster and change nodes format : new cluster format (for viz) + let { name, nodes ,childrenSubgraphs} = subgraphNetwork[TypeSubgraph.MAIN_CHAIN][nameMainChain]; + + if(cycle) { + nodes=changeCycleMetanodes(subgraphNetwork,nodes); + } + + // change format + if (isCluster && !name.startsWith("cluster_")) { + name = "cluster_" + name; + } + const clusterViz: SubgraphViz = { + name: name, + nodes: nodes?.map((name: string) => ({ name:name })) || [] + }; + + //add node of children subgraph !!!!!BEWARE : only one level of children!!!! + if (childrenSubgraphs){ + childrenSubgraphs.forEach(subgraph => { + const subgraphs=subgraphNetwork[subgraph.type]; + if (subgraphs && subgraphs[subgraph.name]){ + let nodeToAdd =subgraphs[subgraph.name].nodes; + if(cycle){ + nodeToAdd=changeCycleMetanodes(subgraphNetwork,nodeToAdd); + } + if (!clusterViz.nodes) clusterViz.nodes = []; + clusterViz.nodes.push(...nodeToAdd.map((name: string) => ({ name:name }))); // add and change format + } + }); + } + + + // push cluster for viz + if (!vizGraph.subgraphs) { + vizGraph.subgraphs = []; + } + vizGraph.subgraphs.push(clusterViz); + + return vizGraph; +} + + +/** + * Changes list of nodes to replace nodes in cycle by the metanode. + * + * @param subgraphNetwork - The subgraph network. + * @param listNodeBefore - The list of nodes before the change. + * @returns The list of nodes after the change. + * @throws {Error} If a node is not found in the network. + */ +function changeCycleMetanodes(subgraphNetwork:SubgraphNetwork,listNodeBefore:string[]):string[]{ + const network=subgraphNetwork.network.value; + const listNodeAfter:string[]=[]; + // for each nodes : + listNodeBefore.forEach(node =>{ + // if node is in cycle metanode : + if (!(node in network.nodes)) throw new Error("Node not found in network"); + + let cycle:string; + if (network.nodes[node].metadataLayout && network.nodes[node].metadataLayout[TypeSubgraph.CYCLEGROUP]){ + cycle = network.nodes[node].metadataLayout[TypeSubgraph.CYCLEGROUP]; + //cycle=inBiggerCycle(cycle,subgraphNetwork) + if(!(listNodeAfter.includes(cycle))){ + // push node cycle + listNodeAfter.push(cycle); + } + }else{ + listNodeAfter.push(node); + } + }); + return listNodeAfter; +} + +/** + * Create a subgraph dot representation for viz. + * + * @param subgraph - The subgraph to add the dot representation for. + * @returns The dot representation of the subgraph. + * @throws {Error} If there are no nodes in the subgraph. + */ +export function subgraphDot(subgraph: SubgraphViz): string { + + if (subgraph.nodes) { + let clusterString = `subgraph ${subgraph.name} {\n`; + subgraph.nodes.forEach((node) => { + clusterString+=`${node.name};`; + }); + return clusterString+"}\n"; + }else{ + throw new Error("No nodes in subgraph"); + } + + } \ No newline at end of file diff --git a/src/composables/countIsolatedNodes.ts b/src/composables/countIsolatedNodes.ts deleted file mode 100644 index e6aec4e..0000000 --- a/src/composables/countIsolatedNodes.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Network } from "@metabohub/viz-core/src/types/Network"; - -/** - * Count how many nodes have no edges in the network - * @param network the network - * @returns the number of isolated nodes - */ -export function countIsolatedNodes(network: Network): number { - let nb: number = 0; - const nodesCount: { [x: string]: number} = {}; - Object.keys(network.nodes).forEach(nodeId => { - nodesCount[nodeId] = 0; - }); - - // count number of links for each node - network.links.forEach(link => { - nodesCount[link.source.id] += 1; - nodesCount[link.target.id] += 1; - }); - - /* - // get the nodes - for (const [key, value] of Object.entries(nodesCount)) { - ... - // if value == 0 - // get key - } - */ - Object.values(nodesCount).forEach(count => { - if (count == 0) { - nb += 1; - } - }); - - return nb; -} \ No newline at end of file diff --git a/src/composables/importNetwork.ts b/src/composables/importNetwork.ts index 528dee0..ec7117c 100644 --- a/src/composables/importNetwork.ts +++ b/src/composables/importNetwork.ts @@ -1,93 +1,93 @@ -import type { Ref } from "vue"; -import type { Network } from "@metabohub/viz-core/src/types/Network"; -import type { GraphStyleProperties } from "@metabohub/viz-core/src//types/GraphStyleProperties"; +// import type { Ref } from "vue"; +// import type { Network } from "@metabohub/viz-core/src/types/Network"; +// import type { GraphStyleProperties } from "@metabohub/viz-core/src//types/GraphStyleProperties"; -//import { readJsonGraph } from "./readJson"; -import { readJsonGraph } from "@metabohub/viz-core"; -import { sbml2json } from "./SBMLtoJSON"; +// //import { readJsonGraph } from "./readJson"; +// import { readJsonGraph } from "@metabohub/viz-core"; +// import { sbml2json } from "./SBMLtoJSON"; -/** - * Import network at JSONGraph format from an URL. - * @param url URL to get network data - * @param network Reference to network object - * @param networkStyle Reference to networkStyle object - * @param callbackFunction Function to call after network load (opt) - */ -export function importNetworkFromURL(url: string, network: Ref<Network>, networkStyle: Ref<GraphStyleProperties>, callbackFunction = () => {}): void { - setTimeout(async function() { - let data:string = await getContentFromURL(url); - // Check if the data is XML - if (data.startsWith('<?xml')) { - // Convert XML to JSON - data = JSON.stringify(await sbml2json(data)); - } - const graphData = readJsonGraph(data); - networkStyle.value = graphData.networkStyle; - loadNetwork(graphData.network, network).then(() => { - callbackFunction(); - }); - }, 1); -} +// /** +// * Import network at JSONGraph format from an URL. +// * @param url URL to get network data +// * @param network Reference to network object +// * @param networkStyle Reference to networkStyle object +// * @param callbackFunction Function to call after network load (opt) +// */ +// export function importNetworkFromURL(url: string, network: Ref<Network>, networkStyle: Ref<GraphStyleProperties>, callbackFunction = () => {}): void { +// setTimeout(async function() { +// let data:string = await getContentFromURL(url); +// // Check if the data is XML +// if (data.startsWith('<?xml')) { +// // Convert XML to JSON +// data = JSON.stringify(await sbml2json(data)); +// } +// const graphData = readJsonGraph(data); +// networkStyle.value = graphData.networkStyle; +// loadNetwork(graphData.network, network).then(() => { +// callbackFunction(); +// }); +// }, 1); +// } -/** - * Import network at JSONGraph format from a file. - * @param file File to get network data - * @param network Reference to network object - * @param networkStyle Reference to networkStyle object - * @param callbackFunction Function to call after network load (opt) - */ +// /** +// * Import network at JSONGraph format from a file. +// * @param file File to get network data +// * @param network Reference to network object +// * @param networkStyle Reference to networkStyle object +// * @param callbackFunction Function to call after network load (opt) +// */ -export function importNetworkFromFile(file: File, network: Ref<Network>, networkStyle: Ref<GraphStyleProperties>, callbackFunction = () => {}): void { - const reader = new FileReader(); - reader.onload = async function () { - let data = reader.result as string; - if (data.startsWith('<?xml')) { - // Convert XML to JSON - data = JSON.stringify(await sbml2json(data)); - } - const networkData = readJsonGraph(data); - networkStyle.value = networkData.networkStyle; - loadNetwork(networkData.network, network).then(() => { - callbackFunction(); - }); - } +// export function importNetworkFromFile(file: File, network: Ref<Network>, networkStyle: Ref<GraphStyleProperties>, callbackFunction = () => {}): void { +// const reader = new FileReader(); +// reader.onload = async function () { +// let data = reader.result as string; +// if (data.startsWith('<?xml')) { +// // Convert XML to JSON +// data = JSON.stringify(await sbml2json(data)); +// } +// const networkData = readJsonGraph(data); +// networkStyle.value = networkData.networkStyle; +// loadNetwork(networkData.network, network).then(() => { +// callbackFunction(); +// }); +// } - reader.readAsText(file); -} +// reader.readAsText(file); +// } -/** - * Make async the step where the data are put in network reference. - * That permit to chain with another function like rescale. - * @param data network data - * @param network Reference to network object - */ -async function loadNetwork(data: Network, network: Ref<Network>): Promise<void> { - network.value = data; - } +// /** +// * Make async the step where the data are put in network reference. +// * That permit to chain with another function like rescale. +// * @param data network data +// * @param network Reference to network object +// */ +// async function loadNetwork(data: Network, network: Ref<Network>): Promise<void> { +// network.value = data; +// } -/** - * Fetch url to return data - * @param url URL to fetch - * @returns Return response - */ -export async function getContentFromURL(url: string): Promise<string> { - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error('La requête a échoué avec le statut ' + response.status); - } - const content = await response.text(); - return content; - } catch (error) { - console.error('Une erreur s\'est produite lors de la récupération du contenu du fichier :', error); - throw error; - } - } +// /** +// * Fetch url to return data +// * @param url URL to fetch +// * @returns Return response +// */ +// export async function getContentFromURL(url: string): Promise<string> { +// try { +// const response = await fetch(url); +// if (!response.ok) { +// throw new Error('La requête a échoué avec le statut ' + response.status); +// } +// const content = await response.text(); +// return content; +// } catch (error) { +// console.error('Une erreur s\'est produite lors de la récupération du contenu du fichier :', error); +// throw error; +// } +// } diff --git a/src/composables/useSubgraphs.ts b/src/composables/useSubgraphs.ts deleted file mode 100644 index 1e7a7d8..0000000 --- a/src/composables/useSubgraphs.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { Subgraph, TypeSubgraph } from "../types/Subgraph"; -import { SubgraphNetwork } from "../types/SubgraphNetwork"; -import { Graph } from "@viz-js/viz"; -//import { inBiggerCycle } from "./ConvertFromNetwork"; - -/** - * - */ -export function addMainChainClusterViz(vizGraph: Graph, nameMainChain: string, subgraphNetwork:SubgraphNetwork,cycle:boolean=true,isCluster:boolean=true): Graph { - - // get values from cluster and change nodes format : new cluster format (for viz) - let { name, nodes ,childrenSubgraphs: associatedSubgraphs} = subgraphNetwork.mainChains[nameMainChain]; - if(cycle) { - nodes=changeCycleMetanodes(subgraphNetwork,nodes); - } - - // change format - if (isCluster && !name.startsWith("cluster_")) { - name = "cluster_" + name; - } - const clusterViz: SubgraphViz = { - name: name, - nodes: nodes?.map((name: string) => ({ name:name })) || [] - }; - - //add node of children subgraph !!!!!BEWARE : only one level of children!!!! - if (associatedSubgraphs){ - associatedSubgraphs.forEach(subgraph => { - let nodeToAdd =subgraphNetwork[subgraph.type][subgraph.name].nodes; - if(cycle){ - nodeToAdd=changeCycleMetanodes(subgraphNetwork,nodeToAdd); - } - clusterViz.nodes.push(...nodeToAdd.map((name: string) => ({ name:name }))); // add and change format - }); - } - - - // push cluster for viz - if (!Object.keys(vizGraph).includes("subgraphs")) { - vizGraph.subgraphs = []; - } - vizGraph.subgraphs.push(clusterViz); - - return vizGraph; -} - -function changeCycleMetanodes(subgraphNetwork:SubgraphNetwork,listNodeBefore:string[]):string[]{ - const network=subgraphNetwork.network.value; - const listNodeAfter:string[]=[]; - // for each nodes : - listNodeBefore.forEach(node =>{ - // if node is in cycle metanode : - let cycle:string; - if (network.nodes[node].metadata && network.nodes[node].metadata[TypeSubgraph.CYCLEGROUP]){ - cycle = network.nodes[node].metadata[TypeSubgraph.CYCLEGROUP] as string; - //cycle=inBiggerCycle(cycle,subgraphNetwork) - } - if(cycle!==undefined && !(listNodeAfter.includes(cycle))){ - // push node cycle - listNodeAfter.push(cycle); - } - if (cycle===undefined){ - listNodeAfter.push(node); - } - }) - - return listNodeAfter; -} - -export function addClusterDot(subgraph: SubgraphViz): string { - - - let clusterString = `subgraph ${subgraph.name} {\n`; - // add rank - // if ("rank" in subgraph){ - // clusterString+=`{rank="${subgraph.rank}";`; - // } - - // add nodes - subgraph.nodes.forEach((node) => { - clusterString+=`${node.name};`; - }); - // if ("rank" in subgraph){ - // clusterString+=`}\n`; - // } - return clusterString+"}\n"; - } - - - - -// export function addNoConstraint(subgraphNetwork:SubgraphNetwork):SubgraphNetwork{ -// let network=subgraphNetwork.network.value; -// network.links.forEach(link=>{ -// let clusterSource: string[] = []; -// let clusterTarget: string[] = []; -// if ( Object.keys(link.source).includes("metadata") && Object.keys(link.source.metadata).includes("clusters")){ -// clusterSource= link.source.metadata?.clusters ? link.source.metadata.clusters as string[] : []; -// } - -// if ( Object.keys(link.target).includes("metadata") && Object.keys(link.target.metadata).includes("clusters")){ -// clusterTarget= link.target.metadata?.clusters ? link.target.metadata.clusters as string[] : []; -// } -// let sameClusters=true; -// // if same number of cluster : let's check if there are the same -// if (clusterTarget.length===clusterSource.length){ -// clusterTarget.sort; -// clusterSource.sort; -// for (let i = 0; i < clusterTarget.length; ++i) { -// if (clusterTarget[i] !== clusterSource[i]){ -// sameClusters=false; -// } -// } -// }else{ -// // if not the same number of cluster : the two nodes can't be in the exact same clusters -// sameClusters=false; -// } - -// if (!sameClusters){ -// if(!link.metadata){ -// link.metadata={}; -// } -// link.metadata["constraint"]=false; -// } -// }); - -// return subgraphNetwork; -// } - -// export function addBoldLinkMainChain(subgraphNetwork:SubgraphNetwork):SubgraphNetwork{ -// let network=subgraphNetwork.network.value; -// network.links.forEach(link=>{ -// let mainChainSource: string[] = []; -// let mainChainTarget: string[] = []; -// if ( Object.keys(link.source).includes("metadata") && Object.keys(link.source.metadata).includes(TypeSubgraph.MAIN_CHAIN)){ -// mainChainSource= link.source.metadata.mainChains as string[]; -// } - -// if ( Object.keys(link.target).includes("metadata") && Object.keys(link.target.metadata).includes(TypeSubgraph.MAIN_CHAIN)){ -// mainChainTarget= link.target.metadata.mainChains as string[]; -// } -// // let sameClusters=true; -// // // if same number of cluster, and in a cluster: let's check if there are the same -// // if (clusterTarget.length===clusterSource.length && clusterSource.length!==0){ -// // clusterTarget.sort; -// // clusterSource.sort; -// // for (let i = 0; i < clusterTarget.length; ++i) { -// // if (clusterTarget[i] !== clusterSource[i]){ -// // sameClusters=false; -// // } -// // } -// // }else{ -// // // if not the same number of cluster : the two nodes can't be in the exact same clusters -// // sameClusters=false; -// // } - -// // Check if there is at least one common cluster -// let commonMainChain = mainChainSource.some(mainchain => mainChainTarget.includes(mainchain)); - -// if (commonMainChain){ -// if(!link.classes){ -// link.classes=[]; -// } -// if (!(link.classes.includes(TypeSubgraph.MAIN_CHAIN))){ -// link.classes.push(TypeSubgraph.MAIN_CHAIN); -// } -// }else{ -// if(link.classes){ -// link.classes = link.classes.filter((c) => c !== TypeSubgraph.MAIN_CHAIN); -// } -// } -// }); - -// return subgraphNetwork; -// } - - - -// export function addRedLinkcycleGroup(subgraphNetwork:SubgraphNetwork):SubgraphNetwork{ -// let network=subgraphNetwork.network.value; -// network.links.forEach(link=>{ -// let cycleSource: string; -// let cycleTarget: string; -// if ( Object.keys(link.source).includes("metadata") && Object.keys(link.source.metadata).includes(TypeSubgraph.CYCLEGROUP)){ -// cycleSource=link.source.metadata[TypeSubgraph.CYCLEGROUP] as string; -// } - -// if ( Object.keys(link.target).includes("metadata") && Object.keys(link.target.metadata).includes(TypeSubgraph.CYCLEGROUP)){ -// cycleTarget= link.target.metadata[TypeSubgraph.CYCLEGROUP] as string; -// } -// if (cycleSource && cycleSource===cycleTarget){ -// if(!link.classes){ -// link.classes=[]; -// } -// if (!(link.classes.includes(TypeSubgraph.CYCLEGROUP))){ -// link.classes.push(TypeSubgraph.CYCLEGROUP); -// } -// }else{ -// if(link.classes){ -// link.classes = link.classes.filter((c) => c !== TypeSubgraph.CYCLEGROUP); -// } -// } -// }); - -// return subgraphNetwork; -// } - -- GitLab From 0515b67018f76c8d42c10419cd181ebf27b143c2 Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Thu, 12 Sep 2024 16:11:49 +0200 Subject: [PATCH 11/13] debut calculate startnode test --- src/composables/CalculateStartNodes.ts | 4 +- .../__tests__/CalculateStartNodes.tests.ts | 247 +++++++++++++----- 2 files changed, 187 insertions(+), 64 deletions(-) diff --git a/src/composables/CalculateStartNodes.ts b/src/composables/CalculateStartNodes.ts index bc9a4e4..c56a1f8 100644 --- a/src/composables/CalculateStartNodes.ts +++ b/src/composables/CalculateStartNodes.ts @@ -54,8 +54,8 @@ export function assignRankOrder(network: NetworkLayout, unique_y: Array<number>, const xNodeByRank: number[][] = Array.from({ length: unique_y.length }, () => []); Object.values(network.nodes).forEach((node) => { const rank = unique_y.indexOf(node.y); - if (!node.metadataLayout) node.metadataLayout={}; if(rank >-1){ + if (!node.metadataLayout) node.metadataLayout={}; node.metadataLayout.rank = rank; if (!onlyRank){ xNodeByRank[rank].push(node.x); @@ -72,7 +72,7 @@ export function assignRankOrder(network: NetworkLayout, unique_y: Array<number>, // get the order for each node Object.values(network.nodes).forEach((node) => { - if (node.metadataLayout && node.metadataLayout.rank){ + if (node.metadataLayout && node.metadataLayout.rank!==undefined){ const rank = node.metadataLayout.rank; if (rank<xNodeByRank.length){ const order = xNodeByRank[rank].indexOf(node.x); diff --git a/src/composables/__tests__/CalculateStartNodes.tests.ts b/src/composables/__tests__/CalculateStartNodes.tests.ts index de95410..4c2d1d7 100644 --- a/src/composables/__tests__/CalculateStartNodes.tests.ts +++ b/src/composables/__tests__/CalculateStartNodes.tests.ts @@ -9,17 +9,42 @@ import * as CalculateStartNodes from '../CalculateStartNodes'; describe('assignRankOrder', () => { let networkLayout: NetworkLayout; + let networkExpectedRank: NetworkLayout; beforeEach(() => { + + const nodes: { [key: string]: NodeLayout } = { + node1: { id: 'node1', x: 1, y: 2, metadataLayout: {} }, + node2: { id: 'node2', x: 2, y: 3, metadataLayout: {} }, + node3: { id: 'node3', x: 3, y: 2 }, + node4: { id: 'node4', x: 3, y: 1, metadataLayout: {} }, + }; + + const nodesRankOrder: { [key: string]: NodeLayout } = { + node1: { id: 'node1', x: 1, y: 2, metadataLayout: {rank:1,order:0} }, + node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:2,order:0} }, + node3: { id: 'node3', x: 3, y: 2, metadataLayout: {rank:1,order:1} }, + node4: { id: 'node4', x: 3, y: 1, metadataLayout: {rank:0,order:0} }, + }; + networkLayout = { id:"test", - nodes: { - node1: { id: 'node1', x: 1, y: 2, metadataLayout: {} }, - node2: { id: 'node2', x: 2, y: 3, metadataLayout: {} }, - node3: { id: 'node3', x: 3, y: 2, metadataLayout: {} }, - node4: { id: 'node4', x: 3, y: 1, metadataLayout: {} }, - }, - links: [] + nodes: nodes, + links: [ + { id:"link",source: nodes.node2, target: nodes.node1 }, + { id:"link",source: nodes.node3, target: nodes.node2 }, + { id:"link",source: nodes.node3, target: nodes.node4 } + ] + }; + + networkExpectedRank = { + id:"test", + nodes:nodesRankOrder, + links: [ + { id:"link",source: nodesRankOrder.node2, target: nodesRankOrder.node1 }, + { id:"link",source: nodesRankOrder.node3, target: nodesRankOrder.node2 }, + { id:"link",source: nodesRankOrder.node3, target: nodesRankOrder.node4 } + ] }; }); @@ -28,12 +53,47 @@ describe('assignRankOrder', () => { it('should assign rank and order to nodes based on their coordinates', () => { // DATA const unique_y = [1,2, 3]; - const networkExpected: NetworkLayout = { + + + // TEST + CalculateStartNodes.assignRankOrder(networkLayout, unique_y, false); + + // EXPECT + expect(networkLayout).toEqual(networkExpectedRank); + }); + + it('should only assign rank', () => { + // DATA + const unique_y = [1,2, 3]; + const networkExpectedRank = { + id:"test", + nodes: { + node1: { id: 'node1', x: 1, y: 2, metadataLayout: {rank:1} }, + node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:2} }, + node3: { id: 'node3', x: 3, y: 2, metadataLayout: {rank:1} }, + node4: { id: 'node4', x: 3, y: 1, metadataLayout: {rank:0} }, + }, + links: [] + }; + + + // TEST + CalculateStartNodes.assignRankOrder(networkLayout, unique_y, true); + + // EXPECT + expect(networkLayout).toEqual(networkExpectedRank); + }); + + + it('should assign rank and order, but not for all y', () => { + // DATA + const unique_y = [1, 3]; + const networkExpectedRank: NetworkLayout = { id:"test", nodes: { - node1: { id: 'node1', x: 1, y: 2, metadataLayout: {rank:1,order:0} }, - node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:2,order:0} }, - node3: { id: 'node3', x: 3, y: 2, metadataLayout: {rank:1,order:1} }, + node1: { id: 'node1', x: 1, y: 2, metadataLayout: {} }, + node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:1,order:0} }, + node3: { id: 'node3', x: 3, y: 2 }, node4: { id: 'node4', x: 3, y: 1, metadataLayout: {rank:0,order:0} }, }, links: [] @@ -43,60 +103,123 @@ describe('assignRankOrder', () => { CalculateStartNodes.assignRankOrder(networkLayout, unique_y, false); // EXPECT - expect(networkLayout).toEqual(networkExpected); + expect(networkLayout).toEqual(networkExpectedRank); }); + it('should assign rank and order, but not for all y (and y associated with no nodes)', () => { + // DATA + const unique_y = [0,1, 1.5,3,6]; + const networkExpectedRank: NetworkLayout = { + id:"test", + nodes: { + node1: { id: 'node1', x: 1, y: 2, metadataLayout: {} }, + node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:3,order:0} }, + node3: { id: 'node3', x: 3, y: 2 }, + node4: { id: 'node4', x: 3, y: 1, metadataLayout: {rank:1,order:0} }, + }, + links: [] + }; + // TEST + CalculateStartNodes.assignRankOrder(networkLayout, unique_y, false); - // it('should assign only rank if onlyRank is true', () => { - // const unique_y = [2, 3]; - // assignRankOrder(networkLayout, unique_y, true); + // EXPECT + expect(networkLayout).toEqual(networkExpectedRank); + }); - // expect(networkLayout.nodes.node1.metadataLayout.rank).toBe(0); - // expect(networkLayout.nodes.node1.metadataLayout.order).toBeUndefined(); - // expect(networkLayout.nodes.node2.metadataLayout.rank).toBe(1); - // expect(networkLayout.nodes.node2.metadataLayout.order).toBeUndefined(); - // expect(networkLayout.nodes.node3.metadataLayout.rank).toBe(0); - // expect(networkLayout.nodes.node3.metadataLayout.order).toBeUndefined(); - // }); -}); -// describe('getStartNodes', () => { -// it('should return all nodes if typeSource is ALL', async () => { -// const result = await getStartNodes(mockNetwork, StartNodesType.ALL); -// expect(result).toEqual(['node1', 'node2', 'node3']); -// }); - -// it('should return nodes with rank 0 if typeSource is RANK_ONLY', async () => { -// const result = await getStartNodes(mockNetwork, StartNodesType.RANK_ONLY); -// expect(result).toEqual(['node1']); -// }); - -// // Add more tests for other StartNodesType values as needed -// }); - -// describe('concatSources', () => { -// it('should concatenate two arrays and remove duplicates', () => { -// const firstSources = ['a', 'b', 'c']; -// const secondSources = ['b', 'c', 'd']; -// const result = concatSources(firstSources, secondSources); - -// expect(result).toEqual(['a', 'b', 'c', 'd']); -// }); - -// it('should return the first array if the second array is empty', () => { -// const firstSources = ['a', 'b', 'c']; -// const secondSources: string[] = []; -// const result = concatSources(firstSources, secondSources); - -// expect(result).toEqual(['a', 'b', 'c']); -// }); - -// it('should return the second array if the first array is empty', () => { -// const firstSources: string[] = []; -// const secondSources = ['a', 'b', 'c']; -// const result = concatSources(firstSources, secondSources); - -// expect(result).toEqual(['a', 'b', 'c']); -// }); -// }); \ No newline at end of file + it('should return start nodes ALL', async () => { + // DATA + + // TEST + + // EXPECT + expect(true).toEqual(false); + }); + + it('should return start nodes RANK_SOURCE_ALL', async () => { + // DATA + + // TEST + + // EXPECT + expect(true).toEqual(false); + }); + + it('should return start nodes RANK_SOURCE', async () => { + // DATA + + // TEST + + // EXPECT + expect(true).toEqual(false); + }); + + it('should return start nodes RANK_ONLY', async () => { + // DATA + + // TEST + + // EXPECT + expect(true).toEqual(false); + }); + + it('should return start nodes SOURCE_ALL', async () => { + // DATA + + // TEST + + // EXPECT + expect(true).toEqual(false); + }); + + it('should return start nodes SOURCE_ONLY', async () => { + // DATA + + // TEST + + // EXPECT + expect(true).toEqual(false); + }); + + + + + it('should concatenate two arrays and remove duplicates', () => { + // DATA + const firstSources = ['a', 'b', 'c']; + const secondSources = ['b', 'c', 'd']; + + // TEST + const result = CalculateStartNodes.concatSources(firstSources, secondSources); + + // EXPECT + expect(result).toEqual(['a', 'b', 'c', 'd']); + }); + + it('should return the first array if the second array is empty', () => { + // DATA + const firstSources = ['a', 'b', 'c']; + const secondSources: string[] = []; + + // TEST + const result = CalculateStartNodes.concatSources(firstSources, secondSources); + + // EXPECT + expect(result).toEqual(['a', 'b', 'c']); + }); + + it('should return the second array if the first array is empty', () => { + // DATA + const firstSources: string[] = []; + const secondSources = ['a', 'b', 'c']; + + // TEST + const result = CalculateStartNodes.concatSources(firstSources, secondSources); + + // EXPECT + expect(result).toEqual(['a', 'b', 'c']); + }); + + +}); -- GitLab From 35f15d89667b65e237ec1bca42e9b55f3702222a Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Fri, 13 Sep 2024 08:37:23 +0200 Subject: [PATCH 12/13] test Calculate start node --- .../__tests__/CalculateStartNodes.tests.ts | 152 ++++++++++++++---- 1 file changed, 122 insertions(+), 30 deletions(-) diff --git a/src/composables/__tests__/CalculateStartNodes.tests.ts b/src/composables/__tests__/CalculateStartNodes.tests.ts index 4c2d1d7..940373c 100644 --- a/src/composables/__tests__/CalculateStartNodes.tests.ts +++ b/src/composables/__tests__/CalculateStartNodes.tests.ts @@ -5,6 +5,8 @@ import { Network } from '@metabohub/viz-core/src/types/Network'; // Composable imports import * as CalculateStartNodes from '../CalculateStartNodes'; +import * as ConvertFromNetwork from '../ConvertFromNetwork'; +import { TypeSubgraph } from '../../types/Subgraph'; describe('assignRankOrder', () => { @@ -13,6 +15,8 @@ describe('assignRankOrder', () => { beforeEach(() => { + // DATA + const nodes: { [key: string]: NodeLayout } = { node1: { id: 'node1', x: 1, y: 2, metadataLayout: {} }, node2: { id: 'node2', x: 2, y: 3, metadataLayout: {} }, @@ -49,6 +53,9 @@ describe('assignRankOrder', () => { }); + afterEach(() => { + jest.clearAllMocks(); + }); it('should assign rank and order to nodes based on their coordinates', () => { // DATA @@ -65,15 +72,20 @@ describe('assignRankOrder', () => { it('should only assign rank', () => { // DATA const unique_y = [1,2, 3]; + const nodesExpected={ + node1: { id: 'node1', x: 1, y: 2, metadataLayout: {rank:1} }, + node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:2} }, + node3: { id: 'node3', x: 3, y: 2, metadataLayout: {rank:1} }, + node4: { id: 'node4', x: 3, y: 1, metadataLayout: {rank:0} }, + } const networkExpectedRank = { id:"test", - nodes: { - node1: { id: 'node1', x: 1, y: 2, metadataLayout: {rank:1} }, - node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:2} }, - node3: { id: 'node3', x: 3, y: 2, metadataLayout: {rank:1} }, - node4: { id: 'node4', x: 3, y: 1, metadataLayout: {rank:0} }, - }, - links: [] + nodes:nodesExpected , + links: [ + { id:"link",source: nodesExpected.node2, target: nodesExpected.node1 }, + { id:"link",source: nodesExpected.node3, target: nodesExpected.node2 }, + { id:"link",source: nodesExpected.node3, target: nodesExpected.node4 } + ] }; @@ -88,15 +100,20 @@ describe('assignRankOrder', () => { it('should assign rank and order, but not for all y', () => { // DATA const unique_y = [1, 3]; + const nodesExpected={ + node1: { id: 'node1', x: 1, y: 2, metadataLayout: {} }, + node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:1,order:0} }, + node3: { id: 'node3', x: 3, y: 2 }, + node4: { id: 'node4', x: 3, y: 1, metadataLayout: {rank:0,order:0} }, + }; const networkExpectedRank: NetworkLayout = { id:"test", - nodes: { - node1: { id: 'node1', x: 1, y: 2, metadataLayout: {} }, - node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:1,order:0} }, - node3: { id: 'node3', x: 3, y: 2 }, - node4: { id: 'node4', x: 3, y: 1, metadataLayout: {rank:0,order:0} }, - }, - links: [] + nodes: nodesExpected, + links: [ + { id:"link",source: nodesExpected.node2, target: nodesExpected.node1 }, + { id:"link",source: nodesExpected.node3, target: nodesExpected.node2 }, + { id:"link",source: nodesExpected.node3, target: nodesExpected.node4 } + ] }; // TEST @@ -109,15 +126,20 @@ describe('assignRankOrder', () => { it('should assign rank and order, but not for all y (and y associated with no nodes)', () => { // DATA const unique_y = [0,1, 1.5,3,6]; + const expectedNode:{[key:string]:NodeLayout}={ + node1: { id: 'node1', x: 1, y: 2, metadataLayout: {} }, + node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:3,order:0} }, + node3: { id: 'node3', x: 3, y: 2 }, + node4: { id: 'node4', x: 3, y: 1, metadataLayout: {rank:1,order:0} }, + } const networkExpectedRank: NetworkLayout = { id:"test", - nodes: { - node1: { id: 'node1', x: 1, y: 2, metadataLayout: {} }, - node2: { id: 'node2', x: 2, y: 3, metadataLayout: {rank:3,order:0} }, - node3: { id: 'node3', x: 3, y: 2 }, - node4: { id: 'node4', x: 3, y: 1, metadataLayout: {rank:1,order:0} }, - }, - links: [] + nodes: expectedNode, + links: [ + { id:"link",source: expectedNode.node2, target: expectedNode.node1 }, + { id:"link",source: expectedNode.node3, target: expectedNode.node2 }, + { id:"link",source: expectedNode.node3, target: expectedNode.node4 } + ] }; // TEST @@ -129,60 +151,130 @@ describe('assignRankOrder', () => { it('should return start nodes ALL', async () => { - // DATA + // DATA + const expectedStartNodes = ['node1', 'node2', 'node3', 'node4']; // TEST + const startNodes=await CalculateStartNodes.getStartNodes(networkExpectedRank,StartNodesType.ALL); // EXPECT - expect(true).toEqual(false); + expect(startNodes).toEqual(expectedStartNodes); }); it('should return start nodes RANK_SOURCE_ALL', async () => { + // MOCK + const networkToGDSGraphMock = jest.spyOn(ConvertFromNetwork, 'networkToGDSGraph'); + networkToGDSGraphMock.mockImplementation(async (network)=>{ + return { + indegree: jest.fn((id: string) => { + if (id === 'node3') return 0; + return 1; + }) + } + }); + // DATA + const expectedStartNodes = ['node4', 'node3', 'node1', 'node2']; // TEST + const startNodes=await CalculateStartNodes.getStartNodes(networkExpectedRank,StartNodesType.RANK_SOURCE_ALL); // EXPECT - expect(true).toEqual(false); + expect(startNodes).toEqual(expectedStartNodes); + expect(networkToGDSGraphMock).toHaveBeenCalledTimes(1); }); it('should return start nodes RANK_SOURCE', async () => { + // MOCK + const networkToGDSGraphMock = jest.spyOn(ConvertFromNetwork, 'networkToGDSGraph'); + networkToGDSGraphMock.mockImplementation(async (network)=>{ + return { + indegree: jest.fn((id: string) => { + if (id === 'node3') return 0; + return 1; + }) + } + }); + // DATA + const expectedStartNodes = ['node4', 'node3']; // TEST + const startNodes=await CalculateStartNodes.getStartNodes(networkExpectedRank,StartNodesType.RANK_SOURCE); // EXPECT - expect(true).toEqual(false); + expect(startNodes).toEqual(expectedStartNodes); + expect(networkToGDSGraphMock).toHaveBeenCalledTimes(1); }); it('should return start nodes RANK_ONLY', async () => { // DATA + const exepectedStartNodes = ['node4']; // TEST + const startNodes=await CalculateStartNodes.getStartNodes(networkExpectedRank,StartNodesType.RANK_ONLY); // EXPECT - expect(true).toEqual(false); + expect(startNodes).toEqual(exepectedStartNodes); }); - it('should return start nodes SOURCE_ALL', async () => { + it('should return start nodes RANK_ONLY when no rank', async () => { // DATA + const expectedStartNodes:string[]= []; // TEST + const startNodes=await CalculateStartNodes.getStartNodes(networkLayout,StartNodesType.RANK_ONLY); // EXPECT - expect(true).toEqual(false); + expect(startNodes).toEqual(expectedStartNodes); }); - it('should return start nodes SOURCE_ONLY', async () => { + + it('should return start nodes SOURCE_ALL', async () => { + // MOCK + const networkToGDSGraphMock = jest.spyOn(ConvertFromNetwork, 'networkToGDSGraph'); + networkToGDSGraphMock.mockImplementation(async (network)=>{ + return { + indegree: jest.fn((id: string) => { + if (id === 'node3') return 0; + return 1; + }) + } + }); + // DATA + const expectedStartNodes = ['node3','node1','node2', 'node4']; // TEST + const startNodes=await CalculateStartNodes.getStartNodes(networkExpectedRank,StartNodesType.SOURCE_ALL); // EXPECT - expect(true).toEqual(false); + expect(startNodes).toEqual(expectedStartNodes); + expect(networkToGDSGraphMock).toHaveBeenCalledTimes(1); }); + it('should return start nodes SOURCE_ONLY', async () => { + // MOCK + const networkToGDSGraphMock = jest.spyOn(ConvertFromNetwork, 'networkToGDSGraph'); + networkToGDSGraphMock.mockImplementation(async (network)=>{ + return { + indegree: jest.fn((id: string) => { + if (id === 'node3') return 0; + return 1; + }) + } + }); + + // DATA + const expectedStartNodes = ['node3']; + // TEST + const startNodes=await CalculateStartNodes.getStartNodes(networkExpectedRank,StartNodesType.SOURCE_ONLY); + + // EXPECT + expect(startNodes).toEqual(expectedStartNodes); + expect(networkToGDSGraphMock).toHaveBeenCalledTimes(1); + }); it('should concatenate two arrays and remove duplicates', () => { -- GitLab From ddbfea7da958e498cea868e6d8672b1629ec54ec Mon Sep 17 00:00:00 2001 From: Elora-V <elora95.vigo@gmail.com> Date: Fri, 13 Sep 2024 13:57:40 +0200 Subject: [PATCH 13/13] debut calculate relation cycle test --- src/composables/CalculateRelationCycle.ts | 4 +- .../__tests__/CalculateRelationCycle.tests.ts | 345 ++++++++++++++++++ 2 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 src/composables/__tests__/CalculateRelationCycle.tests.ts diff --git a/src/composables/CalculateRelationCycle.ts b/src/composables/CalculateRelationCycle.ts index ccde993..2ce533a 100644 --- a/src/composables/CalculateRelationCycle.ts +++ b/src/composables/CalculateRelationCycle.ts @@ -4,11 +4,11 @@ import { Link } from "@metabohub/viz-core/src/types/Link"; import { Ordering } from "../types/EnumArgs"; import { TypeSubgraph } from "../types/Subgraph"; import { LinkLayout, NetworkLayout } from "../types/NetworkLayout"; - +import { Coordinate } from "../types/CoordinatesSize"; // Composable imports import { inCycle } from "./GetSetAttributsNodes"; -import { Coordinate } from "../types/CoordinatesSize"; + /** diff --git a/src/composables/__tests__/CalculateRelationCycle.tests.ts b/src/composables/__tests__/CalculateRelationCycle.tests.ts new file mode 100644 index 0000000..3a00b87 --- /dev/null +++ b/src/composables/__tests__/CalculateRelationCycle.tests.ts @@ -0,0 +1,345 @@ +// Type imports +import { SubgraphNetwork } from '../../types/SubgraphNetwork'; +import { Subgraph, TypeSubgraph } from '../../types/Subgraph'; +import { LinkLayout, NetworkLayout, NodeLayout } from '../../types/NetworkLayout'; +import { Coordinate } from '../../types/CoordinatesSize'; +import { GraphStyleProperties } from '@metabohub/viz-core/src/types/GraphStyleProperties'; + +// Composable imports +import * as CalculateRelationCycle from '../CalculateRelationCycle'; +import * as GetSetAttributsNodes from "../GetSetAttributsNodes"; + + +// General imports +import { ref } from 'vue'; // Import the 'ref' function from the 'vue' module +import { link } from 'fs'; + + + +describe('CalculateRelationCycle', () => { + + let subgraphNetwork: SubgraphNetwork; + let nodes: {[key: string]:NodeLayout}; + let links : LinkLayout[]; + let network: NetworkLayout; + + beforeEach(() => { + + nodes={ + node0: { id: 'node0', x: 0, y: 0, metadataLayout: {} }, + node1: { id: 'node1', x: 1, y: 2, metadataLayout: {} }, + node2: { id: 'node2', x: 2, y: 3, metadataLayout: {} }, + node3: { id: 'node3', x: 3, y: 2 }, + node4: { id: 'node4', x: 3, y: 1, metadataLayout: {} }, + }; + links=[ + {id:"link", source: nodes.node2 , target: nodes.node1 }, + {id:"link", source: nodes.node3 , target: nodes.node2 }, + {id:"link", source: nodes.node4 , target: nodes.node3 }, + {id:"link", source: nodes.node3 , target: nodes.node4 }, + {id:"link", source: nodes.node1 , target: nodes.node4 }, + {id:"link", source: nodes.node3 , target: nodes.node0 }, + {id:"link", source: nodes.node0 , target: nodes.node3 } + ]; + network = { + id: "network", + nodes: nodes, + links: links + } + const networkStyle: GraphStyleProperties = {}; + + subgraphNetwork = { + network: ref<NetworkLayout>(network), + networkStyle: ref<GraphStyleProperties>(networkStyle), + }; + + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + +// 0. Get nodes ***************************************************************** + + it('should throw error because cycle group not defined in subgraphNetwork when trying to get node id placed inside ', () => { + // EXPECT + expect(()=>{CalculateRelationCycle.getNodesIDPlacedInGroupCycle(subgraphNetwork,"groupCycle")}).toThrow(); + + }); + it('should get nodes id placed inside group cycle ', () => { + // DATA + const cycleGroup:Subgraph={ + name: "groupCycle", + nodes: ["node2","node3","node4"], + precalculatedNodesPosition: { + node2: { x: 2, y: 3 }, + node3: { x: 3, y: 2 } + } + }; + + subgraphNetwork[TypeSubgraph.CYCLEGROUP]={}; + subgraphNetwork[TypeSubgraph.CYCLEGROUP]["groupCycle"] = cycleGroup; + + const nodesIDExpected=["node2","node3"]; + + // TEST + const nodesID=CalculateRelationCycle.getNodesIDPlacedInGroupCycle(subgraphNetwork,"groupCycle"); + + // EXPECT + expect(nodesID).toEqual(nodesIDExpected); + + }); + + it('should throw error because cycle group not defined in subgraphNetwork when trying to get node placed inside ', () => { + // EXPECT + expect(()=>{CalculateRelationCycle.getNodesPlacedInGroupCycle(subgraphNetwork,"groupCycle")}).toThrow(); + + }); + + it('should get nodes placed inside group cycle (fixed or not) ', () => { + // DATA + const cycleGroup:Subgraph={ + name: "groupCycle", + nodes: ["node2","node3","node4"], + precalculatedNodesPosition: { + node2: { x: 2, y: 3 }, + node3: { x: 3, y: 2 } + } + }; + + subgraphNetwork[TypeSubgraph.CYCLEGROUP]={}; + subgraphNetwork[TypeSubgraph.CYCLEGROUP]["groupCycle"] = cycleGroup; + + const nodesExpected=[ + {id:"node2", x:2, y:3}, + {id:"node3", x:3, y:2} + ]; + + const nodesExpectedFixed=[ + {id:"node2", fx:2, fy:3}, + {id:"node3", fx:3, fy:2} + ]; + + // TEST + const nodes=CalculateRelationCycle.getNodesPlacedInGroupCycle(subgraphNetwork,"groupCycle"); + const nodesFixed=CalculateRelationCycle.getNodesPlacedInGroupCycle(subgraphNetwork,"groupCycle",true); + + // EXPECT + expect(nodes).toEqual(nodesExpected); + expect(nodesFixed).toEqual(nodesExpectedFixed); + + }); + + it('should get nodes placed inside group cycle, but no precalculatedNodesPosition ', () => { + // DATA + const cycleGroup:Subgraph={ + name: "groupCycle", + nodes: ["node2","node3","node4"], + }; + + subgraphNetwork[TypeSubgraph.CYCLEGROUP]={}; + subgraphNetwork[TypeSubgraph.CYCLEGROUP]["groupCycle"] = cycleGroup; + + const nodesExpected:{ id: string,x?:number, y?:number, fx?:number, fy?:number }[]=[]; + + // TEST + const nodes=CalculateRelationCycle.getNodesPlacedInGroupCycle(subgraphNetwork,"groupCycle"); + + // EXPECT + expect(nodes).toEqual(nodesExpected); + + }); + + it('should throw error because cycle group not defined in subgraphNetwork when trying to get node placed inside (as object) ', () => { + // EXPECT + expect(()=>{CalculateRelationCycle.getNodesPlacedInGroupCycleAsObject(subgraphNetwork,"groupCycle")}).toThrow(); + + }); + + it('should get nodes placed inside group cycle (as object) ', () => { + // DATA + const cycleGroup:Subgraph={ + name: "groupCycle", + nodes: ["node2","node3","node4"], + precalculatedNodesPosition: { + node2: { x: 2, y: 3 }, + node3: { x: 3, y: 2 } + } + }; + + subgraphNetwork[TypeSubgraph.CYCLEGROUP]={}; + subgraphNetwork[TypeSubgraph.CYCLEGROUP]["groupCycle"] = cycleGroup; + + const nodesExpected={ + node2: { x: 2, y: 3 }, + node3: { x: 3, y: 2 } + }; + + // TEST + const nodes=CalculateRelationCycle.getNodesPlacedInGroupCycleAsObject(subgraphNetwork,"groupCycle"); + + // EXPECT + expect(nodes).toEqual(nodesExpected); + + }); + + it('should get nodes placed inside group cycle (as object), but no precalculatedNodesPosition ', () => { + // DATA + const cycleGroup:Subgraph={ + name: "groupCycle", + nodes: ["node2","node3","node4"], + }; + + subgraphNetwork[TypeSubgraph.CYCLEGROUP]={}; + subgraphNetwork[TypeSubgraph.CYCLEGROUP]["groupCycle"] = cycleGroup; + + const nodesExpected:{[key:string]:Coordinate}={}; + + // TEST + const nodes=CalculateRelationCycle.getNodesPlacedInGroupCycleAsObject(subgraphNetwork,"groupCycle"); + + // EXPECT + expect(nodes).toEqual(nodesExpected); + + }); + + it('should return child of a groupcycle, that are not in a cycle ', () => { + // MOCK + const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); + inCycleMock.mockImplementation( (network,id)=>{ + return id==="node2" || id==="node3" || id==="node0"; + }); + + // DATA + const listNodes=["node2","node3"]; + + const childrenExpected=[ + ["node1"], + ["node4"] + ]; + + // TEST + const children=CalculateRelationCycle.childNodeNotInCycle(subgraphNetwork,listNodes); + + // EXPECT + expect(children).toEqual(childrenExpected); + + + }); + + it('should return parent of a groupcycle, that are not in a cycle ', () => { + // MOCK + const inCycleMock = jest.spyOn(GetSetAttributsNodes, 'inCycle'); + inCycleMock.mockImplementation( (network,id)=>{ + return id==="node2" || id==="node3" || id==="node0"; + }); + + // DATA + const listNodes=["node2","node3"]; + + const parentExpected=[ + [], + ["node4"] + ]; + + // TEST + const parent=CalculateRelationCycle.parentNodeNotInCycle(subgraphNetwork,listNodes); + + // EXPECT + expect(parent).toEqual(parentExpected); + + + }); + + it('sould throw an error instead of returning parent/child of a group cycle , because no groupcycle', () => { + // EXPECT + expect(()=>{CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","parent")}).toThrow(); + expect(()=>{CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","child")}).toThrow(); + }); + + it('sould return parent/child of a group cycle , but no precalculatedPosition', () => { + // DATA + const cycleGroup:Subgraph={ + name: "groupCycle", + nodes: ["node2","node3","node4"], + }; + + subgraphNetwork[TypeSubgraph.CYCLEGROUP]={}; + subgraphNetwork[TypeSubgraph.CYCLEGROUP]["groupCycle"] = cycleGroup; + + const resultExpected:string[]=[]; + + // TEST + const resultParent=CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","parent"); + const resultChildren=CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","child"); + + // EXPECT + expect(resultParent).toEqual(resultExpected); + expect(resultChildren).toEqual(resultExpected); + + }); + + it('sould return parent/child of a group cycle with neighborsGroupCycle, no sorting', () => { + // DATA + const cycleGroup:Subgraph={ + name: "groupCycle", + nodes: ["node2","node3","node4"], + }; + + subgraphNetwork[TypeSubgraph.CYCLEGROUP]={}; + subgraphNetwork[TypeSubgraph.CYCLEGROUP]["groupCycle"] = cycleGroup; + + const resultExpected:string[]=[];// TO DO + + // TEST + const resultParent=CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","parent",false); + const resultChildren=CalculateRelationCycle.neighborsGroupCycle(subgraphNetwork,"groupCycle","child",false); + + // EXPECT + expect(resultParent).toEqual(resultExpected); + expect(resultChildren).toEqual(resultExpected); + + }); + + + +// 1. Get (ordered) links ***************************************************************** + + it('should return the link with nodes as metanodes if it s the case', () => { + expect(true).toEqual(false); + }); + + + it('should return the link with nodes as metanodes if it s the case', () => { + + // DATA + nodes.node1.metadataLayout={}; + nodes.node2.metadataLayout={ [TypeSubgraph.CYCLEGROUP]: "groupCycle" }; + nodes.node3.metadataLayout={ [TypeSubgraph.CYCLEGROUP]: "groupCycle" }; + nodes.node4.metadataLayout={ [TypeSubgraph.CYCLEGROUP]: undefined}; + + // TEST + CalculateRelationCycle.cycleMetanodeLink(links[0],true); + + expect(true).toEqual(false); + + + }); + + +// getLinksForListNodes +// sortLinksWithAllGroupCycle +// cycleMetanodeLink + + +// 2. Get graph ***************************************************************** + + //getListNodeLinksForCycleGroupAsObject + // getListNodeLinksForCycleGroup + +// 3. Get relation cycle ***************************************************************** + + // parentCycle + + + }); -- GitLab