From 6a68508dc0ce549f6b5f6392871a6ada2181ce48 Mon Sep 17 00:00:00 2001
From: Michael Behrisch <mbehrisch@graphpolaris.com>
Date: Thu, 7 Nov 2024 11:58:50 +0100
Subject: [PATCH] fix: fixes overlapping edges in schema for list layouts

---
 .../lib/data-access/store/schemaSlice.ts      |  1 +
 libs/shared/lib/graph-layout/list-layouts.ts  | 28 +++++++++++++------
 .../lib/mock-data/query-result/mockData.ts    |  3 +-
 .../lib/schema/panel/schema.stories.tsx       | 19 +++++++------
 .../vis/visualizations/tablevis/tablevis.tsx  | 14 +++++-----
 5 files changed, 39 insertions(+), 26 deletions(-)

diff --git a/libs/shared/lib/data-access/store/schemaSlice.ts b/libs/shared/lib/data-access/store/schemaSlice.ts
index 5c79edddf..1204b4bfc 100644
--- a/libs/shared/lib/data-access/store/schemaSlice.ts
+++ b/libs/shared/lib/data-access/store/schemaSlice.ts
@@ -61,6 +61,7 @@ export const schemaSlice = createSlice({
     setSchema: (state, action: PayloadAction<SchemaGraph>) => {
       if (action.payload === undefined) throw new Error('Schema is undefined');
       state.graph = action.payload;
+      state.loading = false;
     },
     clearSchema: (state) => {
       state.graph = new SchemaGraphology().export();
diff --git a/libs/shared/lib/graph-layout/list-layouts.ts b/libs/shared/lib/graph-layout/list-layouts.ts
index 90392896a..f2c30f7e7 100644
--- a/libs/shared/lib/graph-layout/list-layouts.ts
+++ b/libs/shared/lib/graph-layout/list-layouts.ts
@@ -31,6 +31,9 @@ export class ListLayoutFactory implements ILayoutFactory<ListLayoutAlgorithms> {
 
 const Y_OFFSET = 50;
 const X_RELATION_OFFSET = 50;
+const X_RELATION_OVERLAP_OFFSET = X_RELATION_OFFSET * 0.05;
+
+const RELATION_IDENTIFIER = 'relation';
 
 export abstract class ListLayout extends Layout<ListLayoutProvider> {
   protected defaultLayoutSettings = {
@@ -80,7 +83,7 @@ export class ListNodesFirstLayout extends ListLayout {
       boundingBox = { x1: 0, x2: 1000, y1: 0, y2: 1000 };
     }
 
-    const relationNodes = graph.nodes().filter((node) => node.startsWith('Relation'));
+    const relationNodes = graph.nodes().filter((node) => node.toLowerCase().startsWith(RELATION_IDENTIFIER));
     const entityNodes = graph.nodes().filter((node) => !relationNodes.includes(node));
 
     let y = 0;
@@ -90,10 +93,12 @@ export class ListNodesFirstLayout extends ListLayout {
       graph.updateNodeAttribute(node, 'y', () => y);
     });
 
+    let nodeXIncrement = 0;
     relationNodes.map((node, index) => {
       const relationsY = y + Y_OFFSET + index * Y_OFFSET;
-      graph.updateNodeAttribute(node, 'x', () => boundingBox.x1);
+      graph.updateNodeAttribute(node, 'x', () => boundingBox.x1 + nodeXIncrement);
       graph.updateNodeAttribute(node, 'y', () => relationsY);
+      nodeXIncrement += X_RELATION_OVERLAP_OFFSET;
     });
 
     if (this.verbose) {
@@ -124,14 +129,16 @@ export class ListEdgesFirstLayout extends ListLayout {
       boundingBox = { x1: 0, x2: 1000, y1: 0, y2: 1000 };
     }
 
-    const relationNodes = graph.nodes().filter((node) => node.startsWith('Relation'));
+    const relationNodes = graph.nodes().filter((node) => node.toLowerCase().startsWith(RELATION_IDENTIFIER));
     const entityNodes = graph.nodes().filter((node) => !relationNodes.includes(node));
 
     let y = 0;
+    let nodeXIncrement = 0;
     relationNodes.map((node, index) => {
-      y = index * Y_OFFSET;
-      graph.updateNodeAttribute(node, 'x', () => boundingBox.x1);
+      y = index * Y_OFFSET + nodeXIncrement;
+      graph.updateNodeAttribute(node, 'x', () => +X_RELATION_OFFSET + nodeXIncrement);
       graph.updateNodeAttribute(node, 'y', () => y);
+      nodeXIncrement += X_RELATION_OVERLAP_OFFSET;
     });
 
     entityNodes.map((node, index) => {
@@ -168,7 +175,7 @@ export class ListIntersectedLayout extends ListLayout {
       boundingBox = { x1: 0, x2: 1000, y1: 0, y2: 1000 };
     }
 
-    const relationNodes = graph.nodes().filter((node) => node.startsWith('Relation'));
+    const relationNodes = graph.nodes().filter((node) => node.toLowerCase().startsWith(RELATION_IDENTIFIER));
     const entityNodes = graph.nodes().filter((node) => !relationNodes.includes(node));
 
     const graphAllNodes = graph.nodes();
@@ -187,13 +194,16 @@ export class ListIntersectedLayout extends ListLayout {
     });
 
     let y = 0;
+    let nodeXIncrement = 0;
     intersectedList.map((node, index) => {
       y = index * Y_OFFSET;
+
       graph.updateNodeAttribute(node, 'x', () => {
-        if (node.startsWith('Relation')) {
-          return boundingBox.x1 + X_RELATION_OFFSET;
+        nodeXIncrement += X_RELATION_OVERLAP_OFFSET;
+        if (node.toLowerCase().startsWith(RELATION_IDENTIFIER)) {
+          return boundingBox.x1 + X_RELATION_OFFSET + nodeXIncrement;
         } else {
-          return boundingBox.x1;
+          return boundingBox.x1 + nodeXIncrement;
         }
       });
       graph.updateNodeAttribute(node, 'y', () => y);
diff --git a/libs/shared/lib/mock-data/query-result/mockData.ts b/libs/shared/lib/mock-data/query-result/mockData.ts
index b9df59153..efd19ae6e 100644
--- a/libs/shared/lib/mock-data/query-result/mockData.ts
+++ b/libs/shared/lib/mock-data/query-result/mockData.ts
@@ -21,7 +21,8 @@ export type MockDataI = typeof mockDataArray[number];
 
 
 export const loadMockData = async (fileName: MockDataI) => {
-    const json = await import(`./${fileName.replace('_', '/')}.json` /* @vite-ignore */);
+    const filename = `./${fileName.replace('_', '/')}.json`;
+    const json = await import(filename /* @vite-ignore */);
     const { nodes, edges, metaData } = graphQueryBackend2graphQuery(json.default);
     return {
         data: {
diff --git a/libs/shared/lib/schema/panel/schema.stories.tsx b/libs/shared/lib/schema/panel/schema.stories.tsx
index 3fc4cf458..355421220 100644
--- a/libs/shared/lib/schema/panel/schema.stories.tsx
+++ b/libs/shared/lib/schema/panel/schema.stories.tsx
@@ -1,11 +1,10 @@
-import React from 'react';
-import { Meta } from '@storybook/react';
-import { SchemaUtils } from '@graphpolaris/shared/lib/schema/schema-utils';
 import { schemaSlice, setSchema } from '@graphpolaris/shared/lib/data-access/store';
+import { movieSchemaRaw, northwindSchemaRaw, twitterSchemaRaw } from '@graphpolaris/shared/lib/mock-data';
+import { SchemaUtils } from '@graphpolaris/shared/lib/schema/schema-utils';
 import { configureStore } from '@reduxjs/toolkit';
+import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import { Schema } from './Schema';
-import { movieSchemaRaw } from '@graphpolaris/shared/lib/mock-data';
 
 const Component: Meta<typeof Schema> = {
   /* 👇 The title prop is optional.
@@ -100,11 +99,14 @@ export const TestTooltip = {
   },
 };
 
-
 export const TestMovieSchema = {
   play: async () => {
+    console.log('TestMovieSchema');
     const dispatch = Mockstore.dispatch;
-    const schema = SchemaUtils.schemaBackend2Graphology(movieSchemaRaw);
+
+    const data = await movieSchemaRaw;
+    console.log('data', data);
+    const schema = SchemaUtils.schemaBackend2Graphology(data);
 
     dispatch(setSchema(schema.export()));
   },
@@ -113,17 +115,16 @@ export const TestMovieSchema = {
 export const TestNorthWindSchema = {
   play: async () => {
     const dispatch = Mockstore.dispatch;
-    const schema = SchemaUtils.schemaBackend2Graphology(northwindSchemaRaw);
+    const schema = await SchemaUtils.schemaBackend2Graphology(northwindSchemaRaw);
 
     dispatch(setSchema(schema.export()));
   },
 };
 
-
 export const TestTwitterSchema = {
   play: async () => {
     const dispatch = Mockstore.dispatch;
-    const schema = SchemaUtils.schemaBackend2Graphology(twitterSchemaRaw);
+    const schema = await SchemaUtils.schemaBackend2Graphology(twitterSchemaRaw);
 
     dispatch(setSchema(schema.export()));
   },
diff --git a/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx b/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx
index d4b7e8026..7a1485bc4 100644
--- a/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx
+++ b/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx
@@ -1,13 +1,13 @@
-import React, { useEffect, useMemo, useRef, forwardRef, useImperativeHandle } from 'react';
-import { Table, AugmentedNodeAttributes } from './components/Table';
-import { VisualizationPropTypes, VISComponentType, VisualizationSettingsPropTypes } from '../../common';
-import { Input } from '@graphpolaris/shared/lib/components/inputs';
-import { SettingsContainer } from '@graphpolaris/shared/lib/vis/components/config';
+import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '@graphpolaris/shared/lib/components/accordion';
 import { Button } from '@graphpolaris/shared/lib/components/buttons';
-import { useSearchResultData } from '@graphpolaris/shared/lib/data-access';
+import { Input } from '@graphpolaris/shared/lib/components/inputs';
 import { EntityPill } from '@graphpolaris/shared/lib/components/pills/Pill';
-import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '@graphpolaris/shared/lib/components/accordion';
+import { useSearchResultData } from '@graphpolaris/shared/lib/data-access';
+import { SettingsContainer } from '@graphpolaris/shared/lib/vis/components/config';
 import html2canvas from 'html2canvas';
+import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
+import { VISComponentType, VisualizationPropTypes, VisualizationSettingsPropTypes } from '../../common';
+import { AugmentedNodeAttributes, Table } from './components/Table';
 
 export interface TableVisHandle {
   exportImageInternal: () => void;
-- 
GitLab