From bd955b67a0bafaa936f85d5139ea531550b00965 Mon Sep 17 00:00:00 2001
From: Leonardo Christino <leomilho@gmail.com>
Date: Wed, 3 Apr 2024 13:07:06 +0200
Subject: [PATCH] feat(layout): cytoscape bounding-box logic

---
 libs/config/src/colors.ts                     |   4 +-
 .../lib/graph-layout/cytoscape-layouts.ts     | 642 ++++++++++--------
 .../lib/graph-layout/graphology-layouts.ts    | 121 +++-
 .../graph-layout/layout-creator-usecase.ts    |   3 +-
 libs/shared/lib/graph-layout/layout.ts        |  25 +-
 libs/shared/lib/graph-layout/types.ts         |  31 +-
 libs/shared/lib/schema/panel/schema.tsx       |  19 +-
 .../lib/schema/pills/edges/node-edge.tsx      |   2 +-
 .../nodelinkvis/components/utils.tsx          |  33 +
 .../components/table.module.scss.d.ts         |   6 -
 10 files changed, 530 insertions(+), 356 deletions(-)
 delete mode 100644 libs/shared/lib/vis/visualizations/table_vis/components/table.module.scss.d.ts

diff --git a/libs/config/src/colors.ts b/libs/config/src/colors.ts
index 6efc5979f..651c21dae 100644
--- a/libs/config/src/colors.ts
+++ b/libs/config/src/colors.ts
@@ -156,8 +156,8 @@ export const tailwindColors = {
 };
 
 export const dataColors = {
-  black: 'hsl(0 0 0%)',
-  white: 'hsl(0 0 100%)',
+  black: 'hsl(0, 0, 0%)',
+  white: 'hsl(0, 0, 100%)',
   blue: {
     5: 'hsl(220 80% 98%)',
     10: 'hsl(220 71% 96%)',
diff --git a/libs/shared/lib/graph-layout/cytoscape-layouts.ts b/libs/shared/lib/graph-layout/cytoscape-layouts.ts
index 7d6e8a7ed..9eb00e176 100644
--- a/libs/shared/lib/graph-layout/cytoscape-layouts.ts
+++ b/libs/shared/lib/graph-layout/cytoscape-layouts.ts
@@ -26,7 +26,7 @@ type CytoNode = {
  * This is the Cytoscape Factory
  */
 export class CytoscapeFactory implements ILayoutFactory<CytoscapeLayoutAlgorithms> {
-  createLayout(LayoutAlgorithm: CytoscapeLayoutAlgorithms): Cytoscape | null {
+  createLayout(LayoutAlgorithm: CytoscapeLayoutAlgorithms): CytoscapeLayout | null {
     switch (LayoutAlgorithm) {
       case 'Cytoscape_klay':
         //https://github.com/cytoscape/cytoscape.js-klay
@@ -49,24 +49,65 @@ export class CytoscapeFactory implements ILayoutFactory<CytoscapeLayoutAlgorithm
       case 'Cytoscape_cise':
         //https://github.com/iVis-at-Bilkent/cytoscape.js-cise
         return new CytoscapeCise();
+
+      case 'Cytoscape_grid':
+        //https://js.cytoscape.org/#layouts
+        return new CytoscapeGrid();
+
+      case 'Cytoscape_circle':
+        //https://js.cytoscape.org/#layouts
+        return new CytoscapeCircle();
+      case 'Cytoscape_concentric':
+        //https://js.cytoscape.org/#layouts
+        return new CytoscapeConcentric();
+
+      case 'Cytoscape_breadthfirst':
+        //https://js.cytoscape.org/#layouts
+        return new CytoscapeBreathFirst();
+
+      case 'Cytoscape_cose':
+        //https://js.cytoscape.org/#layouts
+        return new CytoscapeCose();
       default:
         return null;
     }
   }
 }
 
-export abstract class Cytoscape extends Layout<CytoscapeProvider> {
-  public verbose = false;
+export abstract class CytoscapeLayout extends Layout<CytoscapeProvider> {
+  protected cytoscapeInstance: cytoscape.Core | undefined = undefined;
+
+  protected defaultLayoutSettings = {
+    animate: false,
+    animationDuration: 5000,
+    ready: function () {
+      // console.info('Layout.ready');
+    }, // on layoutready
+    stop: () => {
+      // console.log('Layout.stop');
+      this.updateNodePositions();
+    }, // on layoutstop
+  };
 
   constructor(public override algorithm: LayoutAlgorithm<CytoscapeProvider>) {
     super('Cytoscape', algorithm);
   }
 
-  // public specialCytoscapeFunction() {
-  //   console.log('Only Cytoscape Layouts can do this.');
-  // }
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
+    super.layout(graph, boundingBox);
 
-  // Takes the schema as input and creates a list of nodes and edges in a format that the layouting algorithm can use.
+    const cytonodes: CytoNode[] = this.convertToCytoscapeModel(graph);
+    this.createCytoscapeInstance(cytonodes);
+  }
+
+  /**
+   * Converts a graph to a Cytoscape model.
+   * @param graph The graph to be converted.
+   * @returns An array of CytoNode objects representing the graph in Cytoscape format.
+   */
   public convertToCytoscapeModel(graph: Graph): CytoNode[] {
     const cytonodes: CytoNode[] = [];
 
@@ -96,67 +137,50 @@ export abstract class Cytoscape extends Layout<CytoscapeProvider> {
     return cytonodes;
   }
 
-  public setVerbose(verbose: boolean) {
-    this.verbose = verbose;
+  /**
+   * Retrieves the position of a node in the graph layout.
+   * @param nodeId - The ID of the node.
+   * @returns The position of the node as an object with `x` and `y` coordinates.
+   * @throws Error if the node is not found in the current graph.
+   */
+  public getNodePosition(nodeId: string) {
+    if (!this.cytoscapeInstance) {
+      return { x: 0, y: 0 };
+    }
+    const node = this.cytoscapeInstance.getElementById(nodeId);
+    if (!node) {
+      throw Error('Node not found in current graph, Cannot retrieve layout position.');
+    }
+    return node.position();
   }
 
-  makeCytoscapeInstance(cytonodes: CytoNode[]) {
-    return cytoscape({
-      elements: cytonodes,
+  /**
+   * Retrieves the full layout update.
+   * @returns The updated graph layout or null if the cytoscape instance or graph is not available.
+   */
+  public getFullLayoutUpdate() {
+    if (!this.cytoscapeInstance || !this.graph) {
+      return null;
+    }
+    this.updateNodePositions();
+    return this.graph;
+  }
 
+  createCytoscapeInstance(cytonodes: CytoNode[]) {
+    this.cytoscapeInstance = cytoscape({
+      elements: cytonodes,
       headless: true,
-      styleEnabled: true,
-
-      style: [
-        // the stylesheet for the graph
-        {
-          selector: 'node',
-          style: {
-            label: 'data(id)',
-            width: '150px',
-            height: '50px',
-            shape: 'rectangle',
-            'background-color': '#fff',
-            'text-valign': 'center',
-            color: '#333333',
-            'border-width': 1,
-            'border-color': '#2E1A61',
-            'font-size': '10px',
-            'padding-left': '30px',
-            'padding-right': '30px',
-            'padding-top': '50px',
-            'padding-bottom': '50px',
-          },
-          // selector: 'node',
-          // style: {
-          //   content: 'data(id)',
-          //   // label: 'data(id)',
-          //   shape: 'round-rectangle',
-          //   // width: 'label',
-          //   // height: 200,
-          //   'font-size': '10px',
-          //   'padding-left': '5px',
-          //   'padding-right': '5px',
-          //   'padding-top': '5px',
-          //   'padding-bottom': '5px',
-          // },
-        },
-
-        {
-          selector: 'edge',
-          style: {
-            'line-color': '#ccc',
-            'target-arrow-color': '#ccc',
-            'target-arrow-shape': 'triangle',
-            'curve-style': 'bezier',
-          },
-        },
-      ],
+      styleEnabled: false,
     });
+
+    return this.cytoscapeInstance;
   }
 
-  updateNodePositions(nodes: cytoscape.NodeCollection, graph: Graph) {
-    nodes.forEach((node: cytoscape.NodeSingular) => {
+  protected updateNodePositions() {
+    if (!this.graph || !this.cytoscapeInstance) {
+      return;
+    }
+    this.cytoscapeInstance.nodes().forEach((node: cytoscape.NodeSingular) => {
       if (this.verbose) {
         console.log(node.id(), node.position());
       }
@@ -164,378 +188,406 @@ export abstract class Cytoscape extends Layout<CytoscapeProvider> {
       // graph.setNodeAttribute(node.id(), 'x', node.position().x);
       // graph.setNodeAttribute(node.id(), 'y', node.position().y);
 
-      graph.updateNode(node.id(), (attr) => {
+      if (!this.graph) return;
+
+      if (!this.graph.hasNode(node.id())) {
+        throw Error('Node not found in graph; Cannot update position.');
+      }
+
+      this.graph.updateNode(node.id(), (attr) => {
         return {
           ...attr,
           x: node.position().x,
           y: node.position().y,
         };
       });
-
-      // console.log(
-      //   'width',
-      //   node.numericStyle('width'),
-      //   'height',
-      //   node.numericStyle('height')
-      // );
     });
   }
 }
 
+function getWidth(node: any) {
+  /**
+  Calculate the width of a node given its text label `node.data('lbl')`
+  */
+
+  // Create element with attributes needed to calculate text size
+  const ctx = document.createElement('canvas').getContext('2d');
+  const fStyle = node.pstyle('font-style').strValue;
+  const size = node.pstyle('font-size').pfValue + 'px';
+  const family = node.pstyle('font-family').strValue;
+  const weight = node.pstyle('font-weight').strValue;
+  ctx!.font = fStyle + ' ' + weight + ' ' + size + ' ' + family;
+
+  // For multiple lines, evaluate the width of the largest line
+  const lines = node.data('lbl').split('\n');
+  const lengths = lines.map((a: any) => a.length);
+  const max_line = lengths.indexOf(Math.max(...lengths));
+
+  // User-defined padding
+  const padding = 30;
+
+  return ctx!.measureText(lines[max_line]).width + padding;
+}
+
 /**
  * This is a ConcreteProduct
  */
-class CytoscapeKlay extends Cytoscape {
+class CytoscapeKlay extends CytoscapeLayout {
   constructor() {
     super('Cytoscape_klay');
   }
 
-  public override async layout(graph: Graph<Attributes, Attributes, Attributes>): Promise<void> {
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
     await import('cytoscape-klay').then((m) => {
       cytoscape.use(m.default);
     });
-    const cytonodes: CytoNode[] = this.convertToCytoscapeModel(graph);
+    super.layout(graph, boundingBox);
 
-    const cy = this.makeCytoscapeInstance(cytonodes);
+    const cy = this.cytoscapeInstance;
+    if (!cy) return;
 
     const layout = cy.layout({
+      ...this.defaultLayoutSettings,
+      boundingBox: boundingBox,
       name: 'klay',
-      padding: 40,
+      padding: 5,
       klay: {
         borderSpacing: 40,
         aspectRatio: 0.5,
         compactComponents: true,
       },
-      nodeDimensionsIncludeLabels: true,
-
-      // Constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
-      // boundingBox: undefined,
-
-      ready: function () {
-        // console.info('Layout.ready');
-      }, // on layoutready
-      stop: function () {
-        // console.debug('Layout.stop');
-      }, // on layoutstop
     } as any);
     layout.run();
 
-    const nodes = cy.nodes();
-    this.updateNodePositions(nodes, graph);
-
-    // var options = {
-    //   nodeDimensionsIncludeLabels: false, // Boolean which changes whether label dimensions are included when calculating node dimensions
-    //   fit: true, // Whether to fit
-    //   padding: 20, // Padding on fit
-    //   animate: false, // Whether to transition the node positions
-    //   animateFilter: function( node, i ){ return true; }, // Whether to animate specific nodes when animation is on; non-animated nodes immediately go to their final positions
-    //   animationDuration: 500, // Duration of animation in ms if enabled
-    //   animationEasing: undefined, // Easing of animation if enabled
-    //   transform: function( node, pos ){ return pos; }, // A function that applies a transform to the final node position
-    //   ready: undefined, // Callback on layoutready
-    //   stop: undefined, // Callback on layoutstop
-    //   klay: {
-    //     // Following descriptions taken from http://layout.rtsys.informatik.uni-kiel.de:9444/Providedlayout.html?algorithm=de.cau.cs.kieler.klay.layered
-    //     addUnnecessaryBendpoints: false, // Adds bend points even if an edge does not change direction.
-    //     aspectRatio: 1.6, // The aimed aspect ratio of the drawing, that is the quotient of width by height
-    //     borderSpacing: 20, // Minimal amount of space to be left to the border
-    //     compactComponents: false, // Tries to further compact components (disconnected sub-graphs).
-    //     crossingMinimization: 'LAYER_SWEEP', // Strategy for crossing minimization.
-    //     /* LAYER_SWEEP The layer sweep algorithm iterates multiple times over the layers, trying to find node orderings that minimize the number of crossings. The algorithm uses randomization to increase the odds of finding a good result. To improve its results, consider increasing the Thoroughness option, which influences the number of iterations done. The Randomization seed also influences results.
-    //     INTERACTIVE Orders the nodes of each layer by comparing their positions before the layout algorithm was started. The idea is that the relative order of nodes as it was before layout was applied is not changed. This of course requires valid positions for all nodes to have been set on the input graph before calling the layout algorithm. The interactive layer sweep algorithm uses the Interactive Reference Point option to determine which reference point of nodes are used to compare positions. */
-    //     cycleBreaking: 'GREEDY', // Strategy for cycle breaking. Cycle breaking looks for cycles in the graph and determines which edges to reverse to break the cycles. Reversed edges will end up pointing to the opposite direction of regular edges (that is, reversed edges will point left if edges usually point right).
-    //     /* GREEDY This algorithm reverses edges greedily. The algorithm tries to avoid edges that have the Priority property set.
-    //     INTERACTIVE The interactive algorithm tries to reverse edges that already pointed leftwards in the input graph. This requires node and port coordinates to have been set to sensible values.*/
-    //     direction: 'UNDEFINED', // Overall direction of edges: horizontal (right / left) or vertical (down / up)
-    //     /* UNDEFINED, RIGHT, LEFT, DOWN, UP */
-    //     edgeRouting: 'ORTHOGONAL', // Defines how edges are routed (POLYLINE, ORTHOGONAL, SPLINES)
-    //     edgeSpacingFactor: 0.5, // Factor by which the object spacing is multiplied to arrive at the minimal spacing between edges.
-    //     feedbackEdges: false, // Whether feedback edges should be highlighted by routing around the nodes.
-    //     fixedAlignment: 'NONE', // Tells the BK node placer to use a certain alignment instead of taking the optimal result.  This option should usually be left alone.
-    //     /* NONE Chooses the smallest layout from the four possible candidates.
-    //     LEFTUP Chooses the left-up candidate from the four possible candidates.
-    //     RIGHTUP Chooses the right-up candidate from the four possible candidates.
-    //     LEFTDOWN Chooses the left-down candidate from the four possible candidates.
-    //     RIGHTDOWN Chooses the right-down candidate from the four possible candidates.
-    //     BALANCED Creates a balanced layout from the four possible candidates. */
-    //     inLayerSpacingFactor: 1.0, // Factor by which the usual spacing is multiplied to determine the in-layer spacing between objects.
-    //     layoutHierarchy: false, // Whether the selected layouter should consider the full hierarchy
-    //     linearSegmentsDeflectionDampening: 0.3, // Dampens the movement of nodes to keep the diagram from getting too large.
-    //     mergeEdges: false, // Edges that have no ports are merged so they touch the connected nodes at the same points.
-    //     mergeHierarchyCrossingEdges: true, // If hierarchical layout is active, hierarchy-crossing edges use as few hierarchical ports as possible.
-    //     nodeLayering:'NETWORK_SIMPLEX', // Strategy for node layering.
-    //     /* NETWORK_SIMPLEX This algorithm tries to minimize the length of edges. This is the most computationally intensive algorithm. The number of iterations after which it aborts if it hasn't found a result yet can be set with the Maximal Iterations option.
-    //     LONGEST_PATH A very simple algorithm that distributes nodes along their longest path to a sink node.
-    //     INTERACTIVE Distributes the nodes into layers by comparing their positions before the layout algorithm was started. The idea is that the relative horizontal order of nodes as it was before layout was applied is not changed. This of course requires valid positions for all nodes to have been set on the input graph before calling the layout algorithm. The interactive node layering algorithm uses the Interactive Reference Point option to determine which reference point of nodes are used to compare positions. */
-    //     nodePlacement:'BRANDES_KOEPF', // Strategy for Node Placement
-    //     /* BRANDES_KOEPF Minimizes the number of edge bends at the expense of diagram size: diagrams drawn with this algorithm are usually higher than diagrams drawn with other algorithms.
-    //     LINEAR_SEGMENTS Computes a balanced placement.
-    //     INTERACTIVE Tries to keep the preset y coordinates of nodes from the original layout. For dummy nodes, a guess is made to infer their coordinates. Requires the other interactive phase implementations to have run as well.
-    //     SIMPLE Minimizes the area at the expense of... well, pretty much everything else. */
-    //     randomizationSeed: 1, // Seed used for pseudo-random number generators to control the layout algorithm; 0 means a new seed is generated
-    //     routeSelfLoopInside: false, // Whether a self-loop is routed around or inside its node.
-    //     separateConnectedComponents: true, // Whether each connected component should be processed separately
-    //     spacing: 20, // Overall setting for the minimal amount of space to be left between objects
-    //     thoroughness: 7 // How much effort should be spent to produce a nice layout..
-    //   },
-    //   priority: function( edge ){ return null; }, // Edges with a non-nil value are skipped when greedy edge cycle breaking is enabled
-    // };
-    // cy.layout( options ).run();
+    // all options here: https://github.com/cytoscape/cytoscape.js-klay/blob/master/README.md
   }
 }
 
-function getWidth(node: any) {
-  /**
-  Calculate the width of a node given its text label `node.data('lbl')`
-  */
-
-  // Create element with attributes needed to calculate text size
-  const ctx = document.createElement('canvas').getContext('2d');
-  const fStyle = node.pstyle('font-style').strValue;
-  const size = node.pstyle('font-size').pfValue + 'px';
-  const family = node.pstyle('font-family').strValue;
-  const weight = node.pstyle('font-weight').strValue;
-  ctx!.font = fStyle + ' ' + weight + ' ' + size + ' ' + family;
-
-  // For multiple lines, evaluate the width of the largest line
-  const lines = node.data('lbl').split('\n');
-  const lengths = lines.map((a: any) => a.length);
-  const max_line = lengths.indexOf(Math.max(...lengths));
-
-  // User-defined padding
-  const padding = 30;
-
-  return ctx!.measureText(lines[max_line]).width + padding;
-}
-
 /**
  * This is a ConcreteProduct
  */
-class CytoscapeElk extends Cytoscape {
+class CytoscapeElk extends CytoscapeLayout {
   constructor() {
     super('Cytoscape_elk');
   }
 
-  public override async layout(graph: Graph<Attributes, Attributes, Attributes>, verbose?: boolean): Promise<void> {
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
     //@ts-ignore
     await import('cytoscape-elk').then((m) => {
       cytoscape.use(m.default);
     });
-    const cytonodes: CytoNode[] = this.convertToCytoscapeModel(graph);
+    super.layout(graph, boundingBox);
+
+    const cy = this.cytoscapeInstance;
+    if (!cy) return;
 
-    const cy = this.makeCytoscapeInstance(cytonodes);
-
-    // const options = {
-    //   name: 'elk',
-    //   nodeDimensionsIncludeLabels: false, // Boolean which changes whether label dimensions are included when calculating node dimensions
-    //   fit: true, // Whether to fit
-    //   padding: 20, // Padding on fit
-    //   animate: false, // Whether to transition the node positions
-    //   animateFilter: function (node: any, i: number) {
-    //     return true;
-    //   }, // Whether to animate specific nodes when animation is on; non-animated nodes immediately go to their final positions
-    //   animationDuration: 500, // Duration of animation in ms if enabled
-    //   animationEasing: undefined, // Easing of animation if enabled
-    //   transform: function (node: any, pos: Position) {
-    //     return pos;
-    //   }, // A function that applies a transform to the final node position
-    //   ready: undefined, // Callback on layoutready
-    //   stop: undefined, // Callback on layoutstop
-    //   klay: {
-    //     // Following descriptions taken from http://layout.rtsys.informatik.uni-kiel.de:9444/Providedlayout.html?algorithm=de.cau.cs.kieler.klay.layered
-    //     addUnnecessaryBendpoints: false, // Adds bend points even if an edge does not change direction.
-    //     aspectRatio: 1.6, // The aimed aspect ratio of the drawing, that is the quotient of width by height
-    //     borderSpacing: 20, // Minimal amount of space to be left to the border
-    //     compactComponents: false, // Tries to further compact components (disconnected sub-graphs).
-    //     crossingMinimization: 'LAYER_SWEEP', // Strategy for crossing minimization.
-    //     /* LAYER_SWEEP The layer sweep algorithm iterates multiple times over the layers, trying to find node orderings that minimize the number of crossings. The algorithm uses randomization to increase the odds of finding a good result. To improve its results, consider increasing the Thoroughness option, which influences the number of iterations done. The Randomization seed also influences results.
-    //     INTERACTIVE Orders the nodes of each layer by comparing their positions before the layout algorithm was started. The idea is that the relative order of nodes as it was before layout was applied is not changed. This of course requires valid positions for all nodes to have been set on the input graph before calling the layout algorithm. The interactive layer sweep algorithm uses the Interactive Reference Point option to determine which reference point of nodes are used to compare positions. */
-    //     cycleBreaking: 'GREEDY', // Strategy for cycle breaking. Cycle breaking looks for cycles in the graph and determines which edges to reverse to break the cycles. Reversed edges will end up pointing to the opposite direction of regular edges (that is, reversed edges will point left if edges usually point right).
-    //     /* GREEDY This algorithm reverses edges greedily. The algorithm tries to avoid edges that have the Priority property set.
-    //     INTERACTIVE The interactive algorithm tries to reverse edges that already pointed leftwards in the input graph. This requires node and port coordinates to have been set to sensible values.*/
-    //     direction: 'UNDEFINED', // Overall direction of edges: horizontal (right / left) or vertical (down / up)
-    //     /* UNDEFINED, RIGHT, LEFT, DOWN, UP */
-    //     edgeRouting: 'ORTHOGONAL', // Defines how edges are routed (POLYLINE, ORTHOGONAL, SPLINES)
-    //     edgeSpacingFactor: 0.5, // Factor by which the object spacing is multiplied to arrive at the minimal spacing between edges.
-    //     feedbackEdges: false, // Whether feedback edges should be highlighted by routing around the nodes.
-    //     fixedAlignment: 'NONE', // Tells the BK node placer to use a certain alignment instead of taking the optimal result.  This option should usually be left alone.
-    //     /* NONE Chooses the smallest layout from the four possible candidates.
-    //     LEFTUP Chooses the left-up candidate from the four possible candidates.
-    //     RIGHTUP Chooses the right-up candidate from the four possible candidates.
-    //     LEFTDOWN Chooses the left-down candidate from the four possible candidates.
-    //     RIGHTDOWN Chooses the right-down candidate from the four possible candidates.
-    //     BALANCED Creates a balanced layout from the four possible candidates. */
-    //     inLayerSpacingFactor: 1.0, // Factor by which the usual spacing is multiplied to determine the in-layer spacing between objects.
-    //     layoutHierarchy: false, // Whether the selected layouter should consider the full hierarchy
-    //     linearSegmentsDeflectionDampening: 0.3, // Dampens the movement of nodes to keep the diagram from getting too large.
-    //     mergeEdges: false, // Edges that have no ports are merged so they touch the connected nodes at the same points.
-    //     mergeHierarchyCrossingEdges: true, // If hierarchical layout is active, hierarchy-crossing edges use as few hierarchical ports as possible.
-    //     nodeLayering: 'NETWORK_SIMPLEX', // Strategy for node layering.
-    //     /* NETWORK_SIMPLEX This algorithm tries to minimize the length of edges. This is the most computationally intensive algorithm. The number of iterations after which it aborts if it hasn't found a result yet can be set with the Maximal Iterations option.
-    //     LONGEST_PATH A very simple algorithm that distributes nodes along their longest path to a sink node.
-    //     INTERACTIVE Distributes the nodes into layers by comparing their positions before the layout algorithm was started. The idea is that the relative horizontal order of nodes as it was before layout was applied is not changed. This of course requires valid positions for all nodes to have been set on the input graph before calling the layout algorithm. The interactive node layering algorithm uses the Interactive Reference Point option to determine which reference point of nodes are used to compare positions. */
-    //     nodePlacement: 'BRANDES_KOEPF', // Strategy for Node Placement
-    //     /* BRANDES_KOEPF Minimizes the number of edge bends at the expense of diagram size: diagrams drawn with this algorithm are usually higher than diagrams drawn with other algorithms.
-    //     LINEAR_SEGMENTS Computes a balanced placement.
-    //     INTERACTIVE Tries to keep the preset y coordinates of nodes from the original layout. For dummy nodes, a guess is made to infer their coordinates. Requires the other interactive phase implementations to have run as well.
-    //     SIMPLE Minimizes the area at the expense of... well, pretty much everything else. */
-    //     randomizationSeed: 1, // Seed used for pseudo-random number generators to control the layout algorithm; 0 means a new seed is generated
-    //     routeSelfLoopInside: false, // Whether a self-loop is routed around or inside its node.
-    //     separateConnectedComponents: true, // Whether each connected component should be processed separately
-    //     spacing: 20, // Overall setting for the minimal amount of space to be left between objects
-    //     thoroughness: 7, // How much effort should be spent to produce a nice layout..
-    //   },
-    //   priority: function (edge: any) {
-    //     return null;
-    //   }, // Edges with a non-nil value are skipped when greedy edge cycle breaking is enabled
-    // };
+    // options here https://github.com/cytoscape/cytoscape.js-elk
 
     const layout = cy.layout({
+      ...this.defaultLayoutSettings,
+      boundingBox: boundingBox,
       name: 'elk',
       fit: true,
       ranker: 'longest-path',
       animate: false,
+      padding: 30,
       elk: {
         zoomToFit: true,
-        algorithm: 'mrtree',
+        algorithm: 'layered',
         separateConnectedComponents: false,
       },
-      ready: function () {
-        // console.log('Layout.start');
-      }, // on layoutready
-      stop: function () {
-        // console.log('Layout.stop');
-      }, // on layoutstop
     } as any);
     layout.run();
 
-    this.updateNodePositions(cy.nodes(), graph);
+    this.updateNodePositions();
   }
 }
 
 /**
  * This is a ConcreteProduct
  */
-class CytoscapeDagre extends Cytoscape {
+class CytoscapeDagre extends CytoscapeLayout {
   constructor() {
     super('Cytoscape_dagre');
   }
 
-  public override async layout(graph: Graph<Attributes, Attributes, Attributes>, verbose?: boolean): Promise<void> {
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
     await import('cytoscape-dagre').then((m) => {
       cytoscape.use(m.default);
     });
-    const cytonodes: CytoNode[] = this.convertToCytoscapeModel(graph);
+    super.layout(graph, boundingBox);
 
-    const cy = this.makeCytoscapeInstance(cytonodes);
+    const cy = this.cytoscapeInstance;
+    if (!cy) return;
 
     const layout = cy.layout({
+      ...this.defaultLayoutSettings,
+      boundingBox: boundingBox,
       name: 'dagre',
-      animate: false,
       // acyclicer: 'greedy',
       ranker: 'longest-path',
       spacingFactor: 0.7,
-      ready: function () {
-        // console.log('Layout.start');
-      }, // on layoutready
-      stop: function () {
-        // // console.log('Layout.stop');
-      }, // on layoutstop
     } as any);
     layout.run();
 
-    this.updateNodePositions(cy.nodes(), graph);
+    this.updateNodePositions();
   }
 }
 
 /**
  * This is a ConcreteProduct
  */
-class CytoscapeFCose extends Cytoscape {
+class CytoscapeFCose extends CytoscapeLayout {
   constructor() {
     super('Cytoscape_fcose');
   }
 
-  public override async layout(graph: Graph<Attributes, Attributes, Attributes>, verbose?: boolean): Promise<void> {
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
     await import('cytoscape-fcose').then((m) => cytoscape.use(m.default));
-    const cytonodes: CytoNode[] = this.convertToCytoscapeModel(graph);
+    super.layout(graph, boundingBox);
 
-    const cy = this.makeCytoscapeInstance(cytonodes);
+    const cy = this.cytoscapeInstance;
+    if (!cy) return;
 
     const layout = cy.layout({
+      ...this.defaultLayoutSettings,
+      boundingBox: boundingBox,
+      // boundingBox: { x1: 500, x2: 2000, y1: 1000, y2: 2000 },
       name: 'fcose',
-      animate: false,
-      ready: function () {
-        // // console.log('Layout.start');
-      }, // on layoutready
-      stop: function () {
-        // // console.log('Layout.stop');
-      }, // on layoutstop
     } as any);
     layout.run();
 
-    const nodes = cy.nodes();
-    this.updateNodePositions(nodes, graph);
+    this.updateNodePositions();
   }
 }
 
 /**
  * This is a ConcreteProduct
  */
-class CytoscapeCoseBilkent extends Cytoscape {
+class CytoscapeCoseBilkent extends CytoscapeLayout {
   constructor() {
     super('Cytoscape_cose-bilkent');
   }
 
-  public override async layout(graph: Graph<Attributes, Attributes, Attributes>, verbose?: boolean): Promise<void> {
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
     //@ts-ignore
     await import('cytoscape-cose-bilkent').then((m) => cytoscape.use(m.default));
-    const cytonodes: CytoNode[] = this.convertToCytoscapeModel(graph);
+    super.layout(graph, boundingBox);
+
+    // options here: https://github.com/cytoscape/cytoscape.js-cose-bilkent
 
-    const cy = this.makeCytoscapeInstance(cytonodes);
+    const cy = this.cytoscapeInstance;
+    if (!cy) return;
 
     const layout = cy.layout({
-      name: 'cose-bilkent',
+      ...this.defaultLayoutSettings,
       animate: false,
-      ready: function () {
-        // console.log('Layout.start');
-      }, // on layoutready
-      stop: function () {
-        // console.log('Layout.stop');
-      }, // on layoutstop
+      animationDuration: 10000,
+      boundingBox: boundingBox,
+      name: 'cose-bilkent',
+      randomize: true,
+      idealEdgeLength: 30,
     } as any);
+
     layout.run();
 
-    this.updateNodePositions(cy.nodes(), graph);
+    this.updateNodePositions();
+
+    // cy.on('position', 'node', (event) => {
+    //   const node = event.target;
+    //   // The node's position has changed
+    //   // You can access the new position with node.position()
+    //   console.log(node, event.target.position());
+    //   this.updateNodePositions();
+    // });
   }
 }
 
 /**
  * This is a ConcreteProduct
  */
-class CytoscapeCise extends Cytoscape {
+class CytoscapeCise extends CytoscapeLayout {
   constructor() {
     super('Cytoscape_cise');
   }
 
-  public override async layout(graph: Graph<Attributes, Attributes, Attributes>, verbose?: boolean): Promise<void> {
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
     //@ts-ignore
     await import('cytoscape-cise').then((m) => cytoscape.use(m.default));
-    const cytonodes: CytoNode[] = this.convertToCytoscapeModel(graph);
+    super.layout(graph, boundingBox);
 
-    const cy = this.makeCytoscapeInstance(cytonodes);
+    const cy = this.cytoscapeInstance;
+    if (!cy) return;
 
     const layout = cy.layout({
+      ...this.defaultLayoutSettings,
+      boundingBox: boundingBox,
       name: 'cise',
-      ready: function () {
-        // console.log('Layout.start');
-      }, // on layoutready
-      stop: function () {
-        // console.log('Layout.stop');
-      }, // on layoutstop
     } as any);
+    layout?.run();
+
+    this.updateNodePositions();
+  }
+}
+
+/**
+ * This is a ConcreteProduct
+ */
+class CytoscapeCose extends CytoscapeLayout {
+  constructor() {
+    super('Cytoscape_cose');
+  }
+
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
+    super.layout(graph, boundingBox);
+
+    const cy = this.cytoscapeInstance;
+    if (!cy) return;
+
+    const layout = cy.layout({
+      ...this.defaultLayoutSettings,
+      boundingBox: boundingBox,
+      name: 'cose',
+    } as any);
+    layout?.run();
+
+    this.updateNodePositions();
+  }
+}
+
+/**
+ * This is a ConcreteProduct
+ */
+class CytoscapeGrid extends CytoscapeLayout {
+  constructor() {
+    super('Cytoscape_grid');
+  }
+
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
+    super.layout(graph, boundingBox);
+
+    const cy = this.cytoscapeInstance;
+    if (!cy) return;
+
+    const layout = cy.layout({
+      ...this.defaultLayoutSettings,
+      boundingBox: boundingBox,
+      name: 'grid',
+    } as any);
+    layout?.run();
+
+    this.updateNodePositions();
+  }
+}
+
+/**
+ * This is a ConcreteProduct
+ */
+class CytoscapeCircle extends CytoscapeLayout {
+  constructor() {
+    super('Cytoscape_circle');
+  }
+
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
+    super.layout(graph, boundingBox);
+
+    const cy = this.cytoscapeInstance;
+    if (!cy) return;
+
+    const layout = cy.layout({
+      ...this.defaultLayoutSettings,
+      boundingBox: boundingBox,
+
+      name: 'circle',
+    } as any);
+    layout?.run();
+
+    this.updateNodePositions();
+  }
+}
+
+/**
+ * This is a ConcreteProduct
+ */
+class CytoscapeConcentric extends CytoscapeLayout {
+  constructor() {
+    super('Cytoscape_concentric');
+  }
+
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
+    super.layout(graph, boundingBox);
+
+    const cy = this.cytoscapeInstance;
+    if (!cy) return;
+
+    const layout = cy.layout({
+      ...this.defaultLayoutSettings,
+      boundingBox: boundingBox,
+      name: 'concentric',
+    } as any);
+
+    layout?.run();
+
+    this.updateNodePositions();
+  }
+}
+
+/**
+ * This is a ConcreteProduct
+ */
+class CytoscapeBreathFirst extends CytoscapeLayout {
+  constructor() {
+    super('Cytoscape_breadthfirst');
+  }
+
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number },
+  ): Promise<void> {
+    super.layout(graph, boundingBox);
+
+    const cy = this.cytoscapeInstance;
+    if (!cy) return;
+
+    const layout = cy.layout({
+      ...this.defaultLayoutSettings,
+      boundingBox: this.boundingBox,
+
+      name: 'breadthfirst',
+    } as any);
+    // console.log('CytoscapeBreathFirst.layout', layout);
     layout.run();
 
-    this.updateNodePositions(cy.nodes(), graph);
+    this.updateNodePositions();
   }
 }
diff --git a/libs/shared/lib/graph-layout/graphology-layouts.ts b/libs/shared/lib/graph-layout/graphology-layouts.ts
index f2ccccd6d..46960915b 100644
--- a/libs/shared/lib/graph-layout/graphology-layouts.ts
+++ b/libs/shared/lib/graph-layout/graphology-layouts.ts
@@ -1,6 +1,7 @@
 import Graph from 'graphology';
 import { circular, random } from 'graphology-layout';
 import forceAtlas2, { ForceAtlas2Settings } from 'graphology-layout-forceatlas2';
+import FA2Layout from 'graphology-layout-forceatlas2/worker';
 import noverlap from 'graphology-layout-noverlap';
 import { RandomLayoutOptions } from 'graphology-layout/random';
 import { Attributes } from 'graphology-types';
@@ -15,7 +16,7 @@ export type GraphologyProvider = 'Graphology';
  * https://graphology.github.io/
  */
 export class GraphologyFactory implements ILayoutFactory<GraphologyLayoutAlgorithms> {
-  createLayout(layoutAlgorithm: GraphologyLayoutAlgorithms): Graphology | null {
+  createLayout(layoutAlgorithm: GraphologyLayoutAlgorithms): GraphologyLayout | null {
     switch (layoutAlgorithm) {
       case 'Graphology_random':
         return new GraphologyRandom();
@@ -25,72 +26,88 @@ export class GraphologyFactory implements ILayoutFactory<GraphologyLayoutAlgorit
         return new GraphologyNoverlap();
       case 'Graphology_forceAtlas2':
         return new GraphologyForceAtlas2();
+      case 'Graphology_forceAtlas2_webworker':
+        return new GraphologyForceAtlas2Webworker();
       default:
         return null;
     }
   }
 }
 
-export abstract class Graphology extends Layout<GraphologyProvider> {
-  height = 100;
-  width = 100;
+export abstract class GraphologyLayout extends Layout<GraphologyProvider> {
+  protected defaultLayoutSettings = {
+    dimensions: ['x', 'y'],
+    center: 0.5,
+  };
+
   constructor(public override algorithm: LayoutAlgorithm<GraphologyProvider>) {
     super('Graphology', algorithm);
-    this.setDimensions(100, 200);
   }
 
-  public specialGraphologyFunction() {
-    // graph.forEachNode((node, attr) => {
-    //   graph.setNodeAttribute(node, 'x', 0);
-    //   graph.setNodeAttribute(node, 'y', 0);
-    // });
-  }
+  /**
+   * Retrieves the position of a node in the graph layout.
+   * @param nodeId - The ID of the node.
+   * @returns The position of the node as an object with `x` and `y` coordinates.
+   * @throws Error if the node is not found in the current graph.
+   */
+  public getNodePosition(nodeId: string) {
+    if (this.graph === null) {
+      throw new Error('The graph is not set.');
+    }
 
-  public setDimensions(width = 100, height = 100) {
-    this.width = width;
-    this.height = height;
+    // console.log('Getting position for node:', nodeId, this.graph.getNodeAttributes(nodeId));
+    return this.graph.getNodeAttributes(nodeId);
   }
 }
 
 /**
  * This is a ConcreteProduct
  */
-export class GraphologyCircular extends Graphology {
+export class GraphologyCircular extends GraphologyLayout {
   constructor() {
     super('Graphology_circular');
   }
 
-  public override async layout(graph: Graph<Attributes, Attributes, Attributes>): Promise<void> {
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number }
+  ): Promise<void> {
+    super.layout(graph, boundingBox);
     // To directly assign the positions to the nodes:
     circular.assign(graph, {
-      scale: 100,
+      scale: graph.order * 2,
+      ...this.defaultLayoutSettings,
     });
   }
 }
 
-const DEFAULT_RANDOM_SETTINGS: RandomLayoutOptions = {
-  scale: 250,
-};
 /**
  * This is a ConcreteProduct
  */
-export class GraphologyRandom extends Graphology {
+export class GraphologyRandom extends GraphologyLayout {
   constructor() {
     super('Graphology_random');
   }
 
-  public override async layout(graph: Graph<Attributes, Attributes, Attributes>): Promise<void> {
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number }
+  ): Promise<void> {
+    super.layout(graph, boundingBox);
     // const positions = random(graph);
 
     // To directly assign the positions to the nodes:
-    random.assign(graph, DEFAULT_RANDOM_SETTINGS);
+    random.assign(graph, {
+      scale: graph.order * 10,
+      ...this.defaultLayoutSettings,
+    });
   }
 }
 
 const DEFAULT_NOVERLAP_SETTINGS = {
   margin: 40,
   ratio: 40,
-  // gridSize: 50,
+  gridSize: 50,
 
   // gridSize ?number 20: number of grid cells horizontally and vertically subdivising the graph’s space. This is used as an optimization scheme. Set it to 1 and you will have O(n²) time complexity, which can sometimes perform better with very few nodes.
   // margin ?number 5: margin to keep between nodes.
@@ -102,16 +119,23 @@ const DEFAULT_NOVERLAP_SETTINGS = {
 /**
  * This is a ConcreteProduct
  */
-export class GraphologyNoverlap extends Graphology {
+export class GraphologyNoverlap extends GraphologyLayout {
   constructor() {
     super('Graphology_noverlap');
   }
 
-  public override async layout(graph: Graph<Attributes, Attributes, Attributes>): Promise<void> {
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number }
+  ): Promise<void> {
+    super.layout(graph, boundingBox);
     // To directly assign the positions to the nodes:
     noverlap.assign(graph, {
       maxIterations: 10000,
-      settings: DEFAULT_NOVERLAP_SETTINGS,
+      settings: {
+        ...this.defaultLayoutSettings,
+        ...DEFAULT_NOVERLAP_SETTINGS,
+      },
     });
   }
 }
@@ -137,15 +161,52 @@ const DEFAULT_FORCEATLAS2_SETTINGS: ForceAtlas2Settings = {
 /**
  * This is a ConcreteProduct
  */
-export class GraphologyForceAtlas2 extends Graphology {
+export class GraphologyForceAtlas2 extends GraphologyLayout {
   constructor() {
     super('Graphology_forceAtlas2');
   }
 
-  public override async layout(graph: Graph<Attributes, Attributes, Attributes>): Promise<void> {
+  public override async layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number }
+  ): Promise<void> {
+    super.layout(graph, boundingBox);
+
+    const sensibleSettings = forceAtlas2.inferSettings(graph);
     forceAtlas2.assign(graph, {
+      ...this.defaultLayoutSettings,
       iterations: 500,
-      settings: DEFAULT_FORCEATLAS2_SETTINGS,
+      settings: sensibleSettings,
     });
   }
 }
+
+/**
+ * This is a ConcreteProduct
+ */
+export class GraphologyForceAtlas2Webworker extends GraphologyLayout {
+  constructor() {
+    super('Graphology_forceAtlas2_webworker');
+  }
+
+  public override layout(
+    graph: Graph<Attributes, Attributes, Attributes>,
+    boundingBox?: { x1: number; x2: number; y1: number; y2: number }
+  ): void {
+    super.layout(graph, boundingBox);
+
+    const sensibleSettings = forceAtlas2.inferSettings(graph);
+    const layout = new FA2Layout(graph, {
+      settings: {
+        ...this.defaultLayoutSettings,
+        ...sensibleSettings,
+      },
+    });
+    layout.start();
+
+    // stop the layout after 5 seconds
+    setTimeout(() => {
+      layout.stop();
+    }, 10000);
+  }
+}
diff --git a/libs/shared/lib/graph-layout/layout-creator-usecase.ts b/libs/shared/lib/graph-layout/layout-creator-usecase.ts
index fe9f27479..3c1ca6681 100644
--- a/libs/shared/lib/graph-layout/layout-creator-usecase.ts
+++ b/libs/shared/lib/graph-layout/layout-creator-usecase.ts
@@ -20,6 +20,7 @@ export class LayoutFactory implements ILayoutFactory<AllLayoutAlgorithms> {
     return LayoutAlgorithm.startsWith(startsWith);
   }
 
+  // todo make this static
   createLayout<Algorithm extends AllLayoutAlgorithms>(layoutAlgorithm: Algorithm): AlgorithmToLayoutProvider<Algorithm> {
     if (this.isSpecificAlgorithm<GraphologyLayoutAlgorithms>(layoutAlgorithm, 'Graphology')) {
       return this.graphologyFactory.createLayout(layoutAlgorithm) as AlgorithmToLayoutProvider<Algorithm>;
@@ -29,6 +30,6 @@ export class LayoutFactory implements ILayoutFactory<AllLayoutAlgorithms> {
       return this.cytoscapeFactory.createLayout(layoutAlgorithm) as AlgorithmToLayoutProvider<Algorithm>;
     }
 
-    throw Error('Invalid layout algorithm');
+    throw Error('Invalid layout algorithm ' + layoutAlgorithm);
   }
 }
diff --git a/libs/shared/lib/graph-layout/layout.ts b/libs/shared/lib/graph-layout/layout.ts
index 761994a64..8c9b863d6 100644
--- a/libs/shared/lib/graph-layout/layout.ts
+++ b/libs/shared/lib/graph-layout/layout.ts
@@ -2,11 +2,11 @@
 import Graph from 'graphology';
 import { Providers, LayoutAlgorithm } from './types';
 
-/**
- * This is our Product
- */
-
 export abstract class Layout<provider extends Providers> {
+  protected graph: Graph | null = null;
+  protected verbose: boolean = false;
+  protected boundingBox: { x1: number; x2: number; y1: number; y2: number } = { x1: 0, x2: 0, y1: 1000, y2: 1000 };
+
   constructor(
     public provider: provider,
     public algorithm: LayoutAlgorithm<provider>,
@@ -14,8 +14,21 @@ export abstract class Layout<provider extends Providers> {
     // console.info(`Created the following Layout: ${provider} - ${this.algorithm}`);
   }
 
-  public async layout(graph: Graph, verbose?: boolean) {
-    // console.log(`${this.provider} [${this.algorithm}] layouting now`);
+  public setVerbose(verbose: boolean) {
+    this.verbose = verbose;
+  }
+
+  public async layout(graph: Graph, boundingBox?: { x1: number; x2: number; y1: number; y2: number }) {
+    this.graph = graph;
+
+    if (boundingBox !== undefined) {
+      this.boundingBox = boundingBox;
+      // console.log(`Setting bounding box to ${JSON.stringify(this.boundingBox)}`);
+    }
+
+    if (this.verbose) {
+      console.log(`${this.provider} [${this.algorithm}] layouting now`);
+    }
 
     graph.forEachNode((node) => {
       const attr = graph.getNodeAttributes(node);
diff --git a/libs/shared/lib/graph-layout/types.ts b/libs/shared/lib/graph-layout/types.ts
index eab11f8ed..30e89d889 100644
--- a/libs/shared/lib/graph-layout/types.ts
+++ b/libs/shared/lib/graph-layout/types.ts
@@ -1,14 +1,25 @@
-import { Cytoscape, CytoscapeProvider } from './cytoscape-layouts';
-import { Graphology, GraphologyProvider } from './graphology-layouts';
+import { CytoscapeLayout, CytoscapeProvider } from './cytoscape-layouts';
+import { GraphologyLayout, GraphologyProvider } from './graphology-layouts';
+
+export type GraphologyLayoutAlgorithms =
+  | `Graphology_circular`
+  | `Graphology_random`
+  | `Graphology_noverlap`
+  | `Graphology_forceAtlas2`
+  | `Graphology_forceAtlas2_webworker`;
 
-export type GraphologyLayoutAlgorithms = `Graphology_circular` | `Graphology_random` | `Graphology_noverlap` | `Graphology_forceAtlas2`;
 export type CytoscapeLayoutAlgorithms =
   | 'Cytoscape_klay'
   | 'Cytoscape_dagre'
   | 'Cytoscape_elk'
   | 'Cytoscape_fcose'
   | 'Cytoscape_cose-bilkent'
-  | 'Cytoscape_cise';
+  | 'Cytoscape_cise'
+  | 'Cytoscape_cose'
+  | 'Cytoscape_grid'
+  | 'Cytoscape_circle'
+  | 'Cytoscape_concentric'
+  | 'Cytoscape_breadthfirst';
 
 export type AllLayoutAlgorithms = GraphologyLayoutAlgorithms | CytoscapeLayoutAlgorithms;
 
@@ -16,10 +27,10 @@ export type Providers = GraphologyProvider | CytoscapeProvider;
 export type LayoutAlgorithm<Provider extends Providers> = `${Provider}_${string}`;
 
 export type AlgorithmToLayoutProvider<Algorithm extends AllLayoutAlgorithms> = Algorithm extends GraphologyLayoutAlgorithms
-  ? Graphology
+  ? GraphologyLayout
   : Algorithm extends CytoscapeLayoutAlgorithms
-  ? Cytoscape
-  : Cytoscape | Graphology;
+  ? CytoscapeLayout
+  : CytoscapeLayout | GraphologyLayout;
 
 export enum Layouts {
   KLAY = 'Cytoscape_klay',
@@ -28,8 +39,14 @@ export enum Layouts {
   FCOSE = 'Cytoscape_fcose',
   COSE_BILKENT = 'Cytoscape_cose-bilkent',
   CISE = 'Cytoscape_cise',
+  GRID = 'Cytoscape_grid',
+  COSE = 'Cytoscape_cose',
+  CIRCLE = 'Cytoscape_circle',
+  CONCENTRIC = 'Cytoscape_concentric',
+  BREATHFIRST = 'Cytoscape_breadthfirst',
   RANDOM = 'Graphology_random',
   CIRCULAR = 'Graphology_circular',
   NOVERLAP = 'Graphology_noverlap',
   FORCEATLAS2 = 'Graphology_forceAtlas2',
+  FORCEATLAS2WEBWORKER = 'Graphology_forceAtlas2_webworker',
 }
diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx
index 37ed7444c..4e47cd9e3 100644
--- a/libs/shared/lib/schema/panel/schema.tsx
+++ b/libs/shared/lib/schema/panel/schema.tsx
@@ -1,6 +1,3 @@
-import { useSchemaGraph, useSchemaSettings, useSearchResultSchema, useSessionCache } from '@graphpolaris/shared/lib/data-access/store';
-import { AlgorithmToLayoutProvider, AllLayoutAlgorithms, LayoutFactory } from '@graphpolaris/shared/lib/graph-layout';
-import { schemaExpandRelation, schemaGraphology2Reactflow } from '@graphpolaris/shared/lib/schema/schema-utils';
 import { SmartBezierEdge, SmartStepEdge, SmartStraightEdge } from '@tisoap/react-flow-smart-edge';
 
 import { useEffect, useMemo, useRef, useState } from 'react';
@@ -8,11 +5,9 @@ import ReactFlow, { Edge, Node, ReactFlowInstance, ReactFlowProvider, useEdgesSt
 
 import 'reactflow/dist/style.css';
 
-import { wsSchemaRequest } from '@graphpolaris/shared/lib/data-access/broker/wsSchema';
-import { ConnectionDragLine, ConnectionLine } from '@graphpolaris/shared/lib/querybuilder/pills';
 import { Button } from '../../components/buttons';
 import ControlContainer from '../../components/controls';
-import { wsGetStates } from '../../data-access';
+import { useSchemaGraph, useSchemaSettings, useSearchResultSchema, useSessionCache, wsGetStates, wsSchemaRequest } from '../../data-access';
 import { toSchemaGraphology } from '../../data-access/store/schemaSlice';
 import NodeEdge from '../pills/edges/node-edge';
 import SelfEdge from '../pills/edges/self-edge';
@@ -21,6 +16,10 @@ import { RelationNode } from '../pills/nodes/relation/relation-node';
 import { SchemaDialog } from './schemaDialog';
 import { Fullscreen, Cached, Settings } from '@mui/icons-material';
 import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip';
+import React from 'react';
+import { AlgorithmToLayoutProvider, AllLayoutAlgorithms, LayoutFactory } from '../../graph-layout';
+import { ConnectionLine, ConnectionDragLine } from '../../querybuilder';
+import { schemaExpandRelation, schemaGraphology2Reactflow } from '../schema-utils';
 
 interface Props {
   content?: string;
@@ -55,6 +54,7 @@ export const Schema = (props: Props) => {
   const [auth, setAuth] = useState(props.auth);
 
   const reactFlowInstanceRef = useRef<ReactFlowInstance | null>(null);
+  const reactFlowRef = useRef<HTMLDivElement>(null);
 
   // In case the schema is updated
   const schemaGraph = useSchemaGraph();
@@ -90,10 +90,13 @@ export const Schema = (props: Props) => {
 
     updateLayout();
     const expandedSchema = schemaExpandRelation(schemaGraphology);
-    await layout.current?.layout(expandedSchema);
+    const bounds = reactFlowRef.current?.getBoundingClientRect();
+    const xy = bounds ? { x1: 50, x2: bounds.width - 50, y1: 50, y2: bounds.height - 200 } : { x1: 0, x2: 500, y1: 0, y2: 1000 };
+    await layout.current?.layout(expandedSchema, xy);
     const schemaFlow = schemaGraphology2Reactflow(expandedSchema, settings.connectionType, settings.animatedEdges);
     setNodes(schemaFlow.nodes);
     setEdges(schemaFlow.edges);
+    setTimeout(() => fitView(), 100);
   }
 
   useEffect(() => {
@@ -167,7 +170,7 @@ export const Schema = (props: Props) => {
         <p className="text-sm">No Elements</p>
       ) : (
         <ReactFlowProvider>
-          <div className="h-[calc(100%-.8rem)] w-full">
+          <div className="h-[calc(100%-.8rem)] w-full" ref={reactFlowRef}>
             <ReactFlow
               snapGrid={[10, 10]}
               snapToGrid
diff --git a/libs/shared/lib/schema/pills/edges/node-edge.tsx b/libs/shared/lib/schema/pills/edges/node-edge.tsx
index 6e230d231..691c78f0e 100644
--- a/libs/shared/lib/schema/pills/edges/node-edge.tsx
+++ b/libs/shared/lib/schema/pills/edges/node-edge.tsx
@@ -10,7 +10,7 @@
  * See testing plan for more details.*/
 import React, { useEffect } from 'react';
 import { EdgeProps, getMarkerEnd } from 'reactflow';
-import { getCenter } from '@graphpolaris/shared/lib/schema/schema-utils';
+import { getCenter } from '../../schema-utils';
 /**
  * NodeEdge is used for the edges between the nodes in the schema.
  * It has a path that is altered depending on the algorithm in the SchemaViewModelImpl.
diff --git a/libs/shared/lib/vis/visualizations/nodelinkvis/components/utils.tsx b/libs/shared/lib/vis/visualizations/nodelinkvis/components/utils.tsx
index bb8094ccb..c4ac42c84 100644
--- a/libs/shared/lib/vis/visualizations/nodelinkvis/components/utils.tsx
+++ b/libs/shared/lib/vis/visualizations/nodelinkvis/components/utils.tsx
@@ -9,6 +9,7 @@ import { GraphType, LinkType, NodeType } from '../types';
 export function nodeColor(num: number) {
   // num = num % 4;
   // const col = '#000000';
+  let entityColors = Object.values(tailwindColors.entity);
   const col = tailwindColors.custom.nodes[num % (tailwindColors.custom.nodes.length - 1)];
   return binaryColor(col);
 }
@@ -17,6 +18,38 @@ export function binaryColor(color: string) {
   return Number('0x' + color.replace('#', ''));
 }
 
+export function uniq<T>(items: T[]): T[] {
+  return Array.from(new Set<T>(items));
+}
+
+/**
+ * Converts a hexadecimal color code to a number.
+ *
+ * @param hexColor The hexadecimal color code to convert.
+ * @returns The converted number.
+ */
+export function hexToNumber(hexColor: string) {
+  return parseInt(hexColor.replace('#', ''), 16);
+}
+
+export function hslStringToHex(hsl: string) {
+  // Extract h, s, l from the input string
+
+  const match = hsl.match(/\d+/g);
+  if (!match) return '#000000';
+  let [h, s, l] = match.map(Number);
+
+  l /= 100;
+  const a = (s * Math.min(l, 1 - l)) / 100;
+  const f = (n: number) => {
+    const k = (n + h / 30) % 12;
+    const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
+    return Math.round(255 * color)
+      .toString(16)
+      .padStart(2, '0'); // Convert to Hex and ensure 2 digits
+  };
+  return `#${f(0)}${f(8)}${f(4)}`;
+}
 /**
  * Used when you select nodes.
  * The highlight is drawn in ticked.
diff --git a/libs/shared/lib/vis/visualizations/table_vis/components/table.module.scss.d.ts b/libs/shared/lib/vis/visualizations/table_vis/components/table.module.scss.d.ts
deleted file mode 100644
index b903d4992..000000000
--- a/libs/shared/lib/vis/visualizations/table_vis/components/table.module.scss.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-
-declare const classNames: {
-  readonly 'table-container': 'table-container';
-  readonly table: 'table';
-};
-export = classNames;
-- 
GitLab