diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx
index 1e6aee5f7d6212c03acefb7db3f308a9ce9b3148..2759ac28030569d1152be8f44211f984e282a8b6 100644
--- a/apps/web/src/app/app.tsx
+++ b/apps/web/src/app/app.tsx
@@ -12,7 +12,7 @@ import {
 } from '@graphpolaris/shared/lib/data-access';
 import { WebSocketHandler } from '@graphpolaris/shared/lib/data-access/socket';
 import Broker from '@graphpolaris/shared/lib/data-access/socket/broker';
-import { assignNewGraphQueryResult, useAppDispatch } from '@graphpolaris/shared/lib/data-access/store';
+import { assignNewGraphQueryResult, useAppDispatch, useML, useMLEnabledHash } from '@graphpolaris/shared/lib/data-access/store';
 import {
   GraphQueryResultFromBackend,
   GraphQueryResultFromBackendPayload,
@@ -26,6 +26,8 @@ import { VisualizationPanel } from './panels/Visualization';
 import styles from './app.module.scss';
 import { logout } from '@graphpolaris/shared/lib/data-access/store/authSlice';
 import { SchemaFromBackend } from '@graphpolaris/shared/lib/schema';
+import { LinkPredictionInstance, setMLResult, allMLTypes } from '@graphpolaris/shared/lib/data-access/store/mlSlice';
+import { Resizable } from '@graphpolaris/shared/lib/components/Resizable';
 
 export interface App {}
 
@@ -41,6 +43,8 @@ export function App(props: App) {
   const queryHash = useQuerybuilderHash();
   const ws = useRef(new WebSocketHandler(import.meta.env.VITE_BACKEND_WSS_URL));
   const [authCheck, setAuthCheck] = useState(false);
+  const ml = useML();
+  const mlHash = useMLEnabledHash();
 
   // for testing purposes
   // useEffect(() => {
@@ -50,12 +54,17 @@ export function App(props: App) {
   useEffect(() => {
     // Default
     Broker.instance().subscribe((data: SchemaFromBackend) => dispatch(readInSchemaFromBackend(data)), 'schema_result');
-
     Broker.instance().subscribe((data: GraphQueryResultFromBackendPayload) => dispatch(assignNewGraphQueryResult(data)), 'query_result');
+    allMLTypes.forEach((mlType) => {
+      Broker.instance().subscribe((data: LinkPredictionInstance[]) => dispatch(setMLResult({ type: mlType, result: data })), mlType);
+    });
 
     return () => {
       Broker.instance().unSubscribeAll('schema_result');
       Broker.instance().unSubscribeAll('query_result');
+      allMLTypes.forEach((mlType) => {
+        Broker.instance().unSubscribeAll(mlType);
+      });
     };
   }, []);
 
@@ -85,14 +94,14 @@ export function App(props: App) {
       if (query.nodes.length === 0) {
         dispatch(resetGraphQueryResults());
       } else {
-        api_query.execute(Query2BackendQuery(session.currentDatabase, query));
+        api_query.execute(Query2BackendQuery(session.currentDatabase, query, ml));
       }
     }
   };
 
   useEffect(() => {
     runQuery();
-  }, [queryHash]);
+  }, [queryHash, mlHash]);
 
   return (
     <div className="h-screen w-screen">
@@ -101,25 +110,27 @@ export function App(props: App) {
           <aside className="h-[4rem]">
             <Navbar />
           </aside>
-          <main className="flex w-screen h-[calc(100%-4.2rem)] gap-0.5">
-            <div className="h-full min-w-[35vw] max-w-[35vw] panel">
-              <Schema auth={authCheck} />
-            </div>
-            <div className="h-full min-w-[calc(65vw-0.125rem)] max-w-[calc(65vw-0.125rem)] flex-grow-0">
-              <div className="w-full panel h-[50%]">
-                <VisualizationPanel />
+          <main className="flex w-screen h-[calc(100%-4.2rem)]">
+            <Resizable divisorSize={3} horizontal={true} defaultProportion={0.33}>
+              <div className="h-full w-full panel">
+                <Schema auth={authCheck} />
               </div>
-              <div className="h-0.5"></div>
-              <div className="w-full panel h-[calc(50%-0.125rem)]">
-                {/* <h1>Query Panel</h1> */}
-                <QueryBuilder
-                  onRunQuery={() => {
-                    console.log('Run Query');
-                    runQuery();
-                  }}
-                />
+              <div className="h-full w-full">
+                <Resizable divisorSize={3} horizontal={false}>
+                  <div className="w-full h-full panel">
+                    <VisualizationPanel />
+                  </div>
+                  <div className="w-full h-full panel">
+                    <QueryBuilder
+                      onRunQuery={() => {
+                        console.log('Run Query');
+                        runQuery();
+                      }}
+                    />
+                  </div>
+                </Resizable>
               </div>
-            </div>
+            </Resizable>
           </main>
         </div>
       </div>
diff --git a/libs/config/tailwind.config.js b/libs/config/tailwind.config.js
index 553663a5b770180969d1b721dfa99480697b619c..fa3bfc85e6298c3a96ed5b6ae2d0e7bae8c54a5a 100644
--- a/libs/config/tailwind.config.js
+++ b/libs/config/tailwind.config.js
@@ -10,6 +10,20 @@ export default {
         inter: ['"Inter"', ...defaultTheme.fontFamily.sans],
       },
       colors: tailwindColors,
+      animation: {
+        openmenu: 'openmenu 0.3s ease-out',
+        closemenu: 'closemenu 0.3s ease-out',
+      },
+      keyframes: {
+        openmenu: {
+          '0%': { opacity: '0' },
+          '100%': { opacity: '1' },
+        },
+        closemenu: {
+          '0%': { opacity: '1' },
+          '100%': { opacity: '0' },
+        },
+      },
     },
   },
   plugins: [require('@tailwindcss/typography'), require('daisyui')],
diff --git a/libs/shared/lib/components/Dialog.tsx b/libs/shared/lib/components/Dialog.tsx
index c975753399caf237003d19386079c98dd5e443d9..1dbb2e7714bc7e96fe5b8c531e626e31f0d799c9 100644
--- a/libs/shared/lib/components/Dialog.tsx
+++ b/libs/shared/lib/components/Dialog.tsx
@@ -1,11 +1,12 @@
 import { PropsWithChildren, useEffect, useRef } from 'react';
 
 export type DialogProps = {
-  onClose(): void;
+  onClose: () => void;
   open: boolean;
+  children?: React.ReactNode;
 };
 
-export const Dialog = (props: PropsWithChildren<DialogProps>) => {
+export const Dialog = (props: DialogProps) => {
   const ref = useRef<HTMLDialogElement>(null);
 
   useEffect(() => {
diff --git a/libs/shared/lib/components/Popup.tsx b/libs/shared/lib/components/Popup.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a25f44e5451c931a8feabd58ec0ff74e24d367db
--- /dev/null
+++ b/libs/shared/lib/components/Popup.tsx
@@ -0,0 +1,13 @@
+import { useState } from 'react';
+
+export const Popup = (props: { children: React.ReactNode; open: boolean; vanchor: 'left' | 'right' }) => {
+  return (
+    <>
+      {props.open && (
+        <div className={`absolute ${props.vanchor}-20 z-10 max-w-[20rem] bg-offwhite-100 flex flex-col gap-2 animate-openmenu`}>
+          {props.children}
+        </div>
+      )}
+    </>
+  );
+};
diff --git a/libs/shared/lib/components/Resizable.tsx b/libs/shared/lib/components/Resizable.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fdf5aaa1391f853b86d3c12df21c9ebbab97023a
--- /dev/null
+++ b/libs/shared/lib/components/Resizable.tsx
@@ -0,0 +1,95 @@
+import React, { useEffect, useRef } from 'react';
+import { s } from 'vitest/dist/env-afee91f0';
+
+type Props = {
+  children: React.ReactNode;
+  className?: string;
+  style?: React.CSSProperties;
+  horizontal: boolean;
+  divisorSize: number;
+  defaultProportion?: number;
+};
+
+function convertRemToPixels(rem: number) {
+  return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
+}
+
+export const Resizable = ({ children, className, style, horizontal, divisorSize, defaultProportion, ...props }: Props) => {
+  const ref = useRef<HTMLDivElement>(null);
+  const children2 = children as React.ReactElement[];
+  const [firstSize, setFirstSize] = React.useState<number>(0);
+  const [secondSize, setSecondSize] = React.useState<number>(0);
+  const [dragging, setDragging] = React.useState<boolean>(false);
+
+  useEffect(() => {
+    if (ref.current) {
+      const rect = ref.current.getBoundingClientRect();
+      const _defaultProportion = defaultProportion || 0.5;
+      if (horizontal) {
+        setFirstSize(rect.width * _defaultProportion - divisorSize);
+        setSecondSize(rect.width * (1 / _defaultProportion) - divisorSize);
+      } else {
+        setFirstSize(rect.height * _defaultProportion - divisorSize);
+        setSecondSize(rect.height * (1 / _defaultProportion) - divisorSize);
+      }
+    }
+  }, [ref.current]);
+
+  function onMouseDown(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
+    setDragging(true);
+    window.addEventListener('mouseup', onMouseUp);
+  }
+  const onMouseUp = () => {
+    setDragging(false);
+    window.removeEventListener('mouseup', onMouseUp);
+  };
+  function onMouseMove(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
+    if (dragging) {
+      if (horizontal && ref.current) {
+        const rect = ref.current.getBoundingClientRect();
+        setFirstSize(e.clientX);
+        setSecondSize(rect.width - e.clientX);
+      } else {
+        setFirstSize(e.clientY);
+        setSecondSize(window.innerHeight - e.clientY);
+      }
+    }
+  }
+  function onTouchStart(e: React.TouchEvent<HTMLDivElement>) {
+    setDragging(true);
+  }
+  function onTouchMove(e: React.TouchEvent<HTMLDivElement>) {}
+  function onTouchEnd(e: React.TouchEvent<HTMLDivElement>) {
+    setDragging(false);
+  }
+  function onTouchCancel(e: React.TouchEvent<HTMLDivElement>) {
+    setDragging(false);
+  }
+
+  return (
+    <>
+      {dragging && <div className="absolute top-0 left-0 w-screen h-screen z-10 cursor-grabbing" onMouseMove={onMouseMove}></div>}
+      <div className={` w-full h-full flex ${horizontal ? 'flex-row' : 'flex-col'} ${className}`} style={style} {...props} ref={ref}>
+        {firstSize > 0 && (
+          <>
+            <div className="h-full w-full" style={horizontal ? { maxWidth: firstSize } : { maxHeight: firstSize }}>
+              {children2[0]}
+            </div>
+            <div
+              className={' ' + (horizontal ? 'cursor-col-resize' : 'cursor-row-resize')}
+              style={horizontal ? { minWidth: divisorSize } : { minHeight: divisorSize }}
+              onMouseDown={onMouseDown}
+              onTouchStart={onTouchStart}
+              onTouchMove={onTouchMove}
+              onTouchEnd={onTouchEnd}
+              onTouchCancel={onTouchCancel}
+            ></div>
+            <div className="h-full w-full" style={horizontal ? { maxWidth: secondSize } : { maxHeight: secondSize }}>
+              {children2[1]}
+            </div>
+          </>
+        )}
+      </div>
+    </>
+  );
+};
diff --git a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
index a37a20e2fe5a26f85a5bec4e2de86f75e64905ee..85df8346437d6db9865ac7266b2da4ee10319465 100644
--- a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
+++ b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
@@ -48,7 +48,6 @@ export interface GraphQueryResult {
 
   // Describes what entities there are in this graph query result.
   nodeTypes: string[];
-  mlEdges?: any; // FIXME
 }
 
 // Define the initial state using that type
@@ -65,14 +64,16 @@ export const graphQueryResultSlice = createSlice({
   reducers: {
     assignNewGraphQueryResult: (state, action: PayloadAction<GraphQueryResultFromBackendPayload>) => {
       const payload = action.payload.payload;
-      // console.log('!!!assignNewGraphQueryResult', action.payload.payload);
 
-      // Maybe do some data quality checking and parsing
-      // ...
+      // Only keep one node and one edge per id. This is also done in the backend, but we do it here as well to be sure.
+      const nodeIDs = new Set(payload.nodes.map((node) => node.id));
+      const edgeIDs = new Set(payload.edges.map((edge) => edge.id));
+      let nodes = [...nodeIDs].map((nodeID) => payload.nodes.find((node) => node.id === nodeID) as unknown as Node);
+      let edges = [...edgeIDs].map((edgeID) => payload.edges.find((edge) => edge.id === edgeID) as unknown as Edge);
 
       // Collect all the different nodetypes in the result
       const nodeTypes: string[] = [];
-      payload.nodes = payload.nodes.map((node) => {
+      nodes = nodes.map((node) => {
         let _node = { ...node };
         let nodeType = node.id.split('/')[0];
         let innerLabels = node?.attributes?.labels as string[];
@@ -84,7 +85,7 @@ export const graphQueryResultSlice = createSlice({
         return _node;
       });
 
-      payload.edges = payload.edges.map((edge) => {
+      edges = edges.map((edge) => {
         let _edge = { ...edge };
         let edgeType = edge.id.split('/')[0];
         if (!_edge.id.includes('/')) {
@@ -95,8 +96,8 @@ export const graphQueryResultSlice = createSlice({
       });
 
       // Assign new state
-      state.nodes = payload.nodes as Node[];
-      state.edges = payload.edges as Edge[];
+      state.nodes = nodes;
+      state.edges = edges;
       state.nodeTypes = nodeTypes;
     },
     resetGraphQueryResults: (state) => {
diff --git a/libs/shared/lib/data-access/store/hooks.ts b/libs/shared/lib/data-access/store/hooks.ts
index 7463e73c5ecea81390f9d1fdd3d4e98c87e6e04c..a65837dc8ee70792cd03648be7ff30dbecff6829 100644
--- a/libs/shared/lib/data-access/store/hooks.ts
+++ b/libs/shared/lib/data-access/store/hooks.ts
@@ -10,6 +10,7 @@ import {
 } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
 import { sessionCacheState } from './sessionSlice';
 import { authState } from './authSlice';
+import { allMLEnabled, selectML } from './mlSlice';
 
 // Use throughout your app instead of plain `useDispatch` and `useSelector`
 export const useAppDispatch: () => AppDispatch = useDispatch;
@@ -33,3 +34,7 @@ export const useQuerybuilderHash = () => useAppSelector(selectQuerybuilderHash);
 export const useConfig = () => useAppSelector(configState);
 export const useSessionCache = () => useAppSelector(sessionCacheState);
 export const useAuthorizationCache = () => useAppSelector(authState);
+
+// Machine Learning Slices
+export const useML = () => useAppSelector(selectML);
+export const useMLEnabledHash = () => useAppSelector(allMLEnabled);
diff --git a/libs/shared/lib/data-access/store/index.ts b/libs/shared/lib/data-access/store/index.ts
index 02dc9bb14ef580d5ed5baf6ccaf5d0be5deccff9..154aec4d518e733c223f90b20dbea750d072138a 100644
--- a/libs/shared/lib/data-access/store/index.ts
+++ b/libs/shared/lib/data-access/store/index.ts
@@ -11,6 +11,7 @@ export {
   resetGraphQueryResults,
   graphQueryResultSlice,
 } from './graphQueryResultSlice';
+export { mlSlice } from './mlSlice';
 
 // Exported types
 export type { Node, Edge, GraphQueryResult } from './graphQueryResultSlice';
diff --git a/libs/shared/lib/data-access/store/mlSlice.ts b/libs/shared/lib/data-access/store/mlSlice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9af8487a62e9378af9226303feedc8caf7cb0359
--- /dev/null
+++ b/libs/shared/lib/data-access/store/mlSlice.ts
@@ -0,0 +1,101 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import type { RootState } from './store';
+import { AllLayoutAlgorithms, CytoscapeLayoutAlgorithms } from '@graphpolaris/shared/lib/graph-layout';
+import { SchemaUtils } from '../../schema/schema-utils';
+import { SchemaFromBackend, SchemaGraph, SchemaGraphology } from '../../schema';
+import { s } from 'vitest/dist/env-afee91f0';
+
+/**************************************************************** */
+
+export type MLTypes = 'centrality' | 'linkPrediction' | 'communityDetection' | 'shortestPath';
+export const allMLTypes: MLTypes[] = ['centrality', 'linkPrediction', 'communityDetection', 'shortestPath'];
+export enum MLTypesEnum {
+  CENTRALITY = 'centrality',
+  LINK_PREDICTION = 'linkPrediction',
+  COMMUNITY_DETECTION = 'communityDetection',
+  SHORTEST_PATH = 'shortestPath',
+}
+
+export type LinkPredictionInstance = {
+  attributes: { jaccard_coefficient: number };
+  from: string;
+  to: string;
+  id: string;
+};
+
+export type CommunityDetectionInstance = string[]; // set of ids
+
+export type MLInstance<T> = {
+  enabled: boolean;
+  result: T;
+};
+
+export type CommunityDetection = MLInstance<CommunityDetectionInstance[]> & {
+  jaccard_threshold: number;
+};
+
+export type ShortestPath = {
+  enabled: boolean;
+  result: any;
+  srcNode?: string;
+  trtNode?: string;
+};
+
+export type ML = {
+  [MLTypesEnum.LINK_PREDICTION]: MLInstance<LinkPredictionInstance[]>;
+  [MLTypesEnum.CENTRALITY]: MLInstance<Record<string, number>>;
+  [MLTypesEnum.COMMUNITY_DETECTION]: CommunityDetection;
+  [MLTypesEnum.SHORTEST_PATH]: ShortestPath;
+};
+
+// Define the initial state using that type
+export const mlDefaultState: ML = {
+  linkPrediction: { enabled: false, result: [] },
+  centrality: { enabled: false, result: {} },
+  communityDetection: { enabled: false, result: [], jaccard_threshold: 0.5 },
+  shortestPath: { enabled: false, result: [] },
+};
+export const mlSlice = createSlice({
+  name: 'ml',
+  // `createSlice` will infer the state type from the `initialState` argument
+  initialState: mlDefaultState,
+  reducers: {
+    setLinkPredictionEnabled: (state, action: PayloadAction<boolean>) => {
+      state.linkPrediction.enabled = action.payload;
+    },
+    setCommunityDetectionEnabled: (state, action: PayloadAction<boolean>) => {
+      state.communityDetection.enabled = action.payload;
+    },
+    setCentralityEnabled: (state, action: PayloadAction<boolean>) => {
+      state.centrality.enabled = action.payload;
+    },
+    setShortestPathEnabled: (state, action: PayloadAction<boolean>) => {
+      state.shortestPath.enabled = action.payload;
+    },
+    setShortestPathSource: (state, action: PayloadAction<string | undefined>) => {
+      state.shortestPath.srcNode = action.payload;
+    },
+    setShortestPathTarget: (state, action: PayloadAction<string | undefined>) => {
+      state.shortestPath.trtNode = action.payload;
+    },
+    setMLResult: (state, action: PayloadAction<{ type: MLTypes; result: any[] }>) => {
+      state[action.payload.type].result = action.payload.result;
+    },
+  },
+});
+export const {
+  setMLResult,
+  setLinkPredictionEnabled,
+  setCommunityDetectionEnabled,
+  setCentralityEnabled,
+  setShortestPathSource,
+  setShortestPathTarget,
+  setShortestPathEnabled,
+} = mlSlice.actions;
+
+export const allMLEnabled = (state: RootState): string => {
+  return JSON.stringify(Object.values(state.ml).map((ml) => ml.enabled));
+};
+
+export const selectML = (state: RootState) => state.ml;
+export default mlSlice.reducer;
diff --git a/libs/shared/lib/data-access/store/store.ts b/libs/shared/lib/data-access/store/store.ts
index aa32a4d6aba58d3ae82bddf133279c784acfbd5f..713410cabd3acf056d6def96289b06c7fa3f322b 100644
--- a/libs/shared/lib/data-access/store/store.ts
+++ b/libs/shared/lib/data-access/store/store.ts
@@ -5,6 +5,7 @@ import schemaSlice from './schemaSlice';
 import configSlice from './configSlice';
 import sessionSlice from './sessionSlice';
 import authSlice from './authSlice';
+import mlSlice from './mlSlice';
 
 export const store = configureStore({
   reducer: {
@@ -14,6 +15,7 @@ export const store = configureStore({
     sessionCache: sessionSlice,
     auth: authSlice,
     config: configSlice,
+    ml: mlSlice,
   },
   middleware: (getDefaultMiddleware) =>
     getDefaultMiddleware({
diff --git a/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx b/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx
index 06a26612530935cf68837302d4c8064e4ed0319e..5e514f6ea8aed3ab9a3c38c6eb52f9a188f88fe5 100644
--- a/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx
+++ b/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx
@@ -6,6 +6,7 @@
 
 import { type } from 'os';
 import { AllLogicStatement, AnyStatement, InputNodeType } from './logic/general';
+import { MLTypes } from '../../data-access/store/mlSlice';
 
 /** JSON query format used to send a query to the backend. */
 export interface BackendQueryResultFormat {
@@ -34,7 +35,7 @@ export interface BackendQueryFormat {
   // entities: Entity[];
   // relations: Relation[];
   // groupBys: GroupBy[];
-  // machineLearning: MachineLearning[];
+  machineLearning: MachineLearning[];
   // modifiers: ModifierStruct[];
   // prefix: string;
 }
@@ -168,11 +169,11 @@ export interface Constraint {
 // }
 
 // /** Interface for Machine Learning algorithm */
-// export interface MachineLearning {
-//   ID?: number;
-//   queuename: string;
-//   parameters: string[];
-// }
+export interface MachineLearning {
+  ID?: number;
+  type: MLTypes;
+  parameters?: string[];
+}
 
 // /** Interface for what the JSON needs for link predicition */
 // export interface LinkPrediction {
diff --git a/libs/shared/lib/querybuilder/model/logic/graphFunctions.tsx b/libs/shared/lib/querybuilder/model/logic/graphFunctions.tsx
deleted file mode 100644
index 9ba0343f8eb8cb6a4de3e81c8c12d003ead26c38..0000000000000000000000000000000000000000
--- a/libs/shared/lib/querybuilder/model/logic/graphFunctions.tsx
+++ /dev/null
@@ -1,146 +0,0 @@
-// TODO; move each to its own pill & logic (allowing for more complex and modular functions)
-// /**
-//  * This program has been developed by students from the bachelor Computer Science at
-//  * Utrecht University within the Software Project course.
-//  * © Copyright Utrecht University (Department of Information and Computing Sciences)
-//  */
-
-// /** What functions exist
-//  *  Default is for the functions in the function bar that don't exist yet.
-//  */
-export enum GraphFunctionTypes {
-  //   GroupBy = 'groupBy',
-  link = 'linkPrediction',
-  communityDetection = 'communityDetection',
-  centrality = 'centrality',
-  shortestPath = 'shortestPath',
-  default = 'default',
-}
-
-// export enum FunctionArgTypes {
-//   group = 'group',
-//   by = 'by',
-//   relation = 'relation',
-//   modifier = 'modifier',
-//   constraints = 'constraints',
-//   result = 'result',
-//   ID1 = 'ID1',
-//   ID2 = 'ID2',
-// }
-// /** All arguments that groupby pill needs */
-// export const DefaultGroupByArgs: FunctionArgs = {
-//   group: { displayName: 'Group', connectable: true, value: '', visible: true },
-//   by: { displayName: 'By', connectable: true, value: '_id', visible: true },
-//   relation: {
-//     displayName: 'On',
-//     connectable: true,
-//     value: undefined,
-//     visible: true,
-//   },
-//   modifier: {
-//     displayName: 'Modifier: ',
-//     connectable: false,
-//     value: '',
-//     visible: true,
-//   },
-//   constraints: {
-//     displayName: 'Constraints: ',
-//     connectable: true,
-//     value: undefined,
-//     visible: true,
-//   },
-//   result: {
-//     displayName: 'Result: ',
-//     connectable: true,
-//     value: undefined,
-//     visible: true,
-//   },
-// };
-// /** All arguments that linkprediction pill needs */
-// export const DefaultLinkPredictionArgs: FunctionArgs = {
-//   linkprediction: {
-//     //currently the querybuilder shows this name instead of the display name that needs to be changed.
-//     displayName: 'linkprediction',
-//     connectable: false,
-//     value: undefined,
-//     visible: true,
-//   },
-// };
-
-// /** All arguments that CommunictyDetection pill needs */
-// export const DefaultCommunictyDetectionArgs: FunctionArgs = {
-//   CommunityDetection: {
-//     displayName: 'CommunityDetection',
-//     connectable: false,
-//     value: undefined,
-//     visible: true,
-//   },
-// };
-
-// /** All arguments that centrality pill needs */
-// export const DefaultCentralityArgs: FunctionArgs = {
-//   centrality: {
-//     displayName: 'centrality',
-//     connectable: false,
-//     value: undefined,
-//     visible: true,
-//   },
-// };
-
-// /** All arguments that centrality pill needs */
-// export const DefaultShortestPathArgs: FunctionArgs = {
-//   shortestPath: {
-//     displayName: 'shortestPath',
-//     connectable: false,
-//     value: undefined,
-//     visible: true,
-//   },
-// };
-
-// // TODO: fix this to somehow make use of the enum
-// /** Returns the correct arguments depending on the type */
-// export const DefaultFunctionArgs: { [type: string]: FunctionArgs } = {
-//   groupBy: DefaultGroupByArgs,
-//   linkPrediction: DefaultLinkPredictionArgs,
-//   communityDetection: DefaultCommunictyDetectionArgs,
-//   centrality: DefaultCentralityArgs,
-//   shortestPath: DefaultShortestPathArgs,
-// };
-
-/** Interface for what function descriptions need */
-export interface GraphFunctionDescription {
-  name: string;
-  type: GraphFunctionTypes;
-  description: string;
-}
-
-/** All available functions in the function bar. */
-export const GraphFunctions: Record<string, GraphFunctionDescription> = {
-  centrality: {
-    name: 'centrality',
-    type: GraphFunctionTypes.centrality,
-    description: 'W.I.P. Shows the importance of nodes',
-  },
-  communityDetection: {
-    name: 'Community Detection',
-    type: GraphFunctionTypes.communityDetection,
-    description: 'Group entities connected by a relation based on how interconnected they are.',
-  },
-  //   groupBy: {
-  //     name: 'Group By',
-  //     type: GraphFunctionTypes.GroupBy,
-  //     description:
-  //       'W.I.P. Per entity of type A, generate aggregate statistics of an attribute of either all links of a relation, or all nodes of an entity of type B connected to the type A entity by a relation.',
-  //   },
-  link: {
-    name: 'Link Prediction',
-    type: GraphFunctionTypes.link,
-    description:
-      'For each pair of entities from a given type, determine the overlap between nodes they are connect to by a given relation.',
-  },
-  shortestPath: {
-    name: 'shortestPath',
-    type: GraphFunctionTypes.shortestPath,
-    description: 'W.I.P. shortest path. Shows the shortest path between nodes',
-  },
-};
diff --git a/libs/shared/lib/querybuilder/model/logic/index.ts b/libs/shared/lib/querybuilder/model/logic/index.ts
index 01d4062551732c0ae3a205cdc488389445e92b48..eaaa301bb03bc691e2457e6a5ab64b1e9fa1b665 100644
--- a/libs/shared/lib/querybuilder/model/logic/index.ts
+++ b/libs/shared/lib/querybuilder/model/logic/index.ts
@@ -24,7 +24,6 @@ export const AllLogicMap: Record<string, AllLogicDescriptions> = {
   ...Object.fromEntries(Object.values(StringFunctions).map((x) => [x.key, x])),
 };
 
-export * from './graphFunctions';
 export * from './numberFunctions';
 export * from './numberFilters';
 export * from './stringFunctions';
diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
index d25ee62444aa69b0ac51ad06bf010d171100f954..912712627b3514c3e048538c22d497117f526300 100644
--- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx
+++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
@@ -42,14 +42,23 @@ import { InputNodeType } from '../model/logic/general';
 import { ConnectionDragLine, ConnectionLine, EntityFlowElement, RelationPill } from '../pills';
 import LogicPill from '../pills/customFlowPills/logicpill/logicpill';
 import { dragPillStarted, movePillTo } from '../pills/dragging/dragPill';
-import { QueryBuilderModal } from './querypopup';
-import { QuerySidePanel, QueryBuilderProps } from './querysidepanel';
+import DifferenceIcon from '@mui/icons-material/Difference';
+import LightbulbIcon from '@mui/icons-material/Lightbulb';
+import { Dialog } from '../../components/Dialog';
+import { QueryBuilderLogicPillsPanel } from './querysidepanel/queryBuilderLogicPillsPanel';
+import { QueryBuilderMLPanel } from './querysidepanel/queryBuilderMLPanel';
+import { Popup } from '../../components/Popup';
+
+export type QueryBuilderProps = {
+  onRunQuery?: () => void;
+};
 
 /**
  * This is the main querybuilder component. It is responsible for holding all pills and fire off the visual part of the querybuilder panel logic
  */
 export const QueryBuilderInner = (props: QueryBuilderProps) => {
-  const [openPopup, setOpenPopup] = useState(false);
+  const [openLogicDialog, setOpenLogicDialog] = useState(false);
+  const [openMLDialog, setOpenMLDialog] = useState(false);
   const reactFlowWrapper = useRef<HTMLDivElement>(null);
 
   var nodeTypes = {
@@ -193,8 +202,6 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
     if (data.length == 0 || !reactFlow) return;
 
     const dragData = JSON.parse(data);
-    console.log(reactFlowWrapper);
-
     const bounds = reactFlowWrapper?.current?.getBoundingClientRect() || { x: 0, y: 0 };
     const position = reactFlow.project({
       //TODO: this position should be centre of entity, rather than topleft
@@ -229,17 +236,6 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
           },
           schema.getEdgeAttribute(dragData.label, 'attributes')
         );
-        // const leftEntity = graphologyGraph.addPill2Graphology(
-        //   { type: QueryElementTypes.Entity, ...RelationPosToFromEntityPos(position), name: dragData.from },
-        //   schema.getNodeAttribute(dragData.from, 'attributes')
-        // );
-        // const rightEntity = graphologyGraph.addPill2Graphology(
-        //   { type: QueryElementTypes.Entity, ...RelationPosToToEntityPos(position), name: dragData.to },
-        //   schema.getNodeAttribute(dragData.to, 'attributes')
-        // );
-
-        // graphologyGraph.addEdge2Graphology(leftEntity, relation);
-        // graphologyGraph.addEdge2Graphology(relation, rightEntity);
 
         if (config.autoSendQueries) {
           // sendQuery();
@@ -247,21 +243,11 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
 
         dispatch(setQuerybuilderNodes(graphologyGraph.export()));
         break;
-      // Creates an attribute element with the correct dataType
-      // case QueryElementTypes.Attribute:
-      //   createNodeFromSchema(
-      //     QueryElementTypes.Attribute,
-      //     position,
-      //     dragData.name,
-      //     dragData.datatype
-      //   );
-      //   break;
       default:
         const logic = AllLogicMap[dragData.value.key];
         const firstLeftLogicInput = logic.inputs?.[0];
         if (!firstLeftLogicInput) return;
 
-        // logicAttributes[0].handles = [connectingNodeId.current.handleId];
         const logicNode = graphologyGraph.addLogicPill2Graphology({
           name: dragData.value.name,
           type: QueryElementTypes.Logic,
@@ -310,7 +296,6 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
 
       let node = graphologyGraph.getNodeAttributes(params.nodeId);
       const handleData = toHandleData(params.handleId);
-      // console.log(attributeName, attributeType, node.attributes?.filter((a) => a.name === attributeName)?.[0]);
 
       connectingNodeId.current = {
         params,
@@ -347,47 +332,59 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
         const position = reactFlow.project({ x: clientX, y: clientY });
         if (connectingNodeId?.current) connectingNodeId.current.position = position;
 
-        setOpenPopup(true);
+        setOpenLogicDialog(true);
       }
     },
     [reactFlow.project]
   );
 
   const onNewNodeFromPopup = (value: AllLogicDescriptions) => {
-    setOpenPopup(false);
-    if (connectingNodeId.current === null || connectingNodeId.current?.params?.handleId == null) return;
-    const params = connectingNodeId.current.params;
-    const position = connectingNodeId.current.position;
-
-    // console.log('onNewNodeFromPopup', value, type, params, position);
+    console.log('onNewNodeFromPopup', value);
 
     const logic = AllLogicMap[value.key];
     const firstLeftLogicInput = logic.inputs?.[0];
     if (!firstLeftLogicInput) return;
 
-    // logicAttributes[0].handles = [connectingNodeId.current.handleId];
-    const logicNode = graphologyGraph.addLogicPill2Graphology({
-      name: value.name,
-      type: QueryElementTypes.Logic,
-      x: position.x,
-      y: position.y,
-      logic: logic,
-    });
-
-    if (!logicNode?.id) throw new Error('Logic node has no id');
-    if (!logicNode?.name) throw new Error('Logic node has no name');
-    if (!params.handleId) throw new Error('Connection has no source or target');
-
-    const sourceHandleData = toHandleData(params.handleId);
-    graphologyGraph.addEdge2Graphology(
-      graphologyGraph.getNodeAttributes(params.nodeId),
-      graphologyGraph.getNodeAttributes(logicNode.id),
-      { type: 'connection' },
-      { sourceHandleName: sourceHandleData.attributeName, targetHandleName: firstLeftLogicInput.name }
-    );
+    if (connectingNodeId.current === null || connectingNodeId.current?.params?.handleId == null) {
+      const bounds = reactFlowWrapper?.current?.getBoundingClientRect() || { x: 0, y: 0, width: 0, height: 0 };
+
+      const logicNode = graphologyGraph.addLogicPill2Graphology({
+        name: value.name,
+        type: QueryElementTypes.Logic,
+        x: bounds.width / 2,
+        y: bounds.height / 2,
+        logic: logic,
+      });
+    } else {
+      const params = connectingNodeId.current.params;
+      const position = connectingNodeId.current.position;
+
+      // console.log('onNewNodeFromPopup', value, type, params, position);
+
+      const logicNode = graphologyGraph.addLogicPill2Graphology({
+        name: value.name,
+        type: QueryElementTypes.Logic,
+        x: position.x,
+        y: position.y,
+        logic: logic,
+      });
+
+      if (!logicNode?.id) throw new Error('Logic node has no id');
+      if (!logicNode?.name) throw new Error('Logic node has no name');
+      if (!params.handleId) throw new Error('Connection has no source or target');
+
+      const sourceHandleData = toHandleData(params.handleId);
+      graphologyGraph.addEdge2Graphology(
+        graphologyGraph.getNodeAttributes(params.nodeId),
+        graphologyGraph.getNodeAttributes(logicNode.id),
+        { type: 'connection' },
+        { sourceHandleName: sourceHandleData.attributeName, targetHandleName: firstLeftLogicInput.name }
+      );
+    }
 
     dispatch(setQuerybuilderNodes(graphologyGraph.export()));
-    setOpenPopup(false);
+    setOpenLogicDialog(false);
+    connectingNodeId.current = null;
   };
 
   const onEdgeUpdateStart = useCallback(() => {
@@ -426,7 +423,6 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
           graphologyGraph.dropEdge(edge.id);
         }
         dispatch(setQuerybuilderNodes(graphologyGraph.export()));
-        // setEdges((eds) => eds.filter((e) => e.id !== edge.id));
       }
       isEdgeUpdating.current = false;
     },
@@ -435,21 +431,28 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
 
   const onNodeContextMenu = (event: React.MouseEvent, node: Node) => {
     event.preventDefault();
-    // console.log('context menu', node);
     graphologyGraph.dropNode(node.id);
     dispatch(setQuerybuilderNodes(graphologyGraph.export()));
   };
 
   return (
     <div ref={reactFlowWrapper} className="h-full w-full">
-      <QueryBuilderModal
-        open={openPopup}
-        handle={connectingNodeId.current?.attribute.handleData}
+      <Dialog
+        open={openLogicDialog}
         onClose={() => {
-          setOpenPopup(false);
+          setOpenLogicDialog(false);
         }}
-        onClick={onNewNodeFromPopup}
-      />
+      >
+        <QueryBuilderLogicPillsPanel
+          onClick={onNewNodeFromPopup}
+          title="Logic Pills usable by the node"
+          className="min-h-[75vh] max-h-[75vh]"
+          onDrag={() => {}}
+        />
+      </Dialog>
+      <Popup open={openMLDialog} vanchor="right">
+        <QueryBuilderMLPanel />
+      </Popup>
       <ReactFlow
         edges={elements.edges}
         nodes={elements.nodes}
@@ -492,10 +495,6 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
             title={'Export querybuilder'}
             onClick={(event) => {
               event.stopPropagation();
-              // this.setState({
-              //   ...this.state,
-              //   exportMenuAnchor: event.currentTarget,
-              // });
             }}
           >
             <ExportIcon />
@@ -505,17 +504,13 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
             title={'Other settings'}
             onClick={(event) => {
               event.stopPropagation();
-              // this.setState({
-              //   ...this.state,
-              //   settingsMenuAnchor: event.currentTarget,
-              // });
             }}
           >
             <SettingsIcon />
           </ControlButton>
           <ControlButton
             className={styles.buttons}
-            title={'Run Query'}
+            title={'Re-Run Query'}
             onClick={(event) => {
               event.stopPropagation();
               if (props.onRunQuery) props.onRunQuery();
@@ -523,6 +518,26 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
           >
             <CachedIcon />
           </ControlButton>
+          <ControlButton
+            className={styles.buttons + (openLogicDialog ? ' btn-active' : '')}
+            title={'Logic Pills'}
+            onClick={(event) => {
+              event.stopPropagation();
+              setOpenLogicDialog(!openLogicDialog);
+            }}
+          >
+            <DifferenceIcon />
+          </ControlButton>
+          <ControlButton
+            className={styles.buttons + (openMLDialog ? ' btn-active' : '')}
+            title={'Machine Learning'}
+            onClick={(event) => {
+              event.stopPropagation();
+              setOpenMLDialog(!openMLDialog);
+            }}
+          >
+            <LightbulbIcon />
+          </ControlButton>
         </Controls>
       </ReactFlow>
     </div>
@@ -532,10 +547,10 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
 export const QueryBuilder = (props: QueryBuilderProps) => {
   return (
     <div className="flex w-full h-full">
-      <QuerySidePanel title="Query Panel" draggable />
       <ReactFlowProvider>
         <QueryBuilderInner {...props} />
       </ReactFlowProvider>
+      {/* <QuerySidePanel title="Query Panel" draggable /> */}
     </div>
   );
 };
diff --git a/libs/shared/lib/querybuilder/panel/querypopup.tsx b/libs/shared/lib/querybuilder/panel/querypopup.tsx
deleted file mode 100644
index 1749d96977f42603dfc96eeb43aa9c0c122ceb99..0000000000000000000000000000000000000000
--- a/libs/shared/lib/querybuilder/panel/querypopup.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React, { useEffect } from 'react';
-import Draggable from 'react-draggable';
-import { AllLogicDescriptions, MathFilters, NumberFunctions, QueryGraphEdgeHandle, StringFilters, StringFunctions } from '../model';
-import { InputNodeType } from '../model/logic/general';
-import { QuerySidePanel } from './querysidepanel';
-
-export function QueryBuilderModal(props: {
-  open: boolean;
-  handle: QueryGraphEdgeHandle | undefined;
-  onClose: () => void;
-  onClick: (item: AllLogicDescriptions) => void;
-}) {
-  const modal = React.useRef<HTMLDialogElement>(null);
-
-  useEffect(() => {
-    if (props.open) {
-      modal.current?.showModal();
-    } else {
-      modal.current?.close();
-    }
-  }, [props.open]);
-
-  function onClose() {
-    props.onClose();
-  }
-
-  return (
-    <dialog id="my_modal_1" className="modal " ref={modal} onClose={() => onClose()}>
-      <form method="dialog" className="modal-box h-auto">
-        <QuerySidePanel title="Logic Pills usable by the node" filter={props.handle?.attributeType} onClick={props.onClick} />
-      </form>
-
-      <form method="dialog" className="modal-backdrop">
-        <button>close</button>
-      </form>
-    </dialog>
-  );
-}
diff --git a/libs/shared/lib/querybuilder/panel/querysidepanel.tsx b/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderLogicPillsPanel.tsx
similarity index 87%
rename from libs/shared/lib/querybuilder/panel/querysidepanel.tsx
rename to libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderLogicPillsPanel.tsx
index 1ce5fc49b724d1810acc8ca591f356b46c402a6b..72a4aab296c666754a50ec1fb71c54d3250ccd15 100644
--- a/libs/shared/lib/querybuilder/panel/querysidepanel.tsx
+++ b/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderLogicPillsPanel.tsx
@@ -1,16 +1,17 @@
-import React, { useState } from 'react';
-import { AllLogicDescriptions, AllLogicMap } from '../model';
 import FilterAltIcon from '@mui/icons-material/FilterAlt';
 import FunctionsIcon from '@mui/icons-material/Functions';
 import GridOnIcon from '@mui/icons-material/GridOn';
 import NumbersIcon from '@mui/icons-material/Numbers';
 import AbcIcon from '@mui/icons-material/Abc';
+import { useState } from 'react';
+import { AllLogicDescriptions, AllLogicMap } from '../../model';
 
-export const QuerySidePanel = (props: {
-  title: string;
+export const QueryBuilderLogicPillsPanel = (props: {
+  className?: string;
+  title?: string;
   filter?: string;
-  draggable?: boolean;
-  onClick?: (item: AllLogicDescriptions) => void;
+  onClick: (item: AllLogicDescriptions) => void;
+  onDrag?: (item: AllLogicDescriptions) => void;
 }) => {
   const dataOps = [
     {
@@ -41,7 +42,6 @@ export const QuerySidePanel = (props: {
       icon: <AbcIcon fontSize="small" />,
     },
   ];
-
   const filter = props.filter === 'number' ? 'float' : props.filter;
   const [selectedOp, setSelectedOp] = useState(dataOps.findIndex((item) => item.title === filter) || -1);
   const [selectedType, setSelectedType] = useState(dataTypes.findIndex((item) => item.title === filter) || -1);
@@ -51,11 +51,12 @@ export const QuerySidePanel = (props: {
 
     event.dataTransfer.setData('application/reactflow', JSON.stringify({ value }));
     event.dataTransfer.effectAllowed = 'move';
+    if (props.onDrag) props.onDrag(value);
   };
 
   return (
-    <div className="bg-offwhite-100 h-flex flex flex-col gap-2">
-      <h2 className="menu-title">{props.title}</h2>
+    <div className={props.className + ' card'}>
+      {props.title && <h1 className="card-title mb-7">{props.title}</h1>}
       <div className="btn-group w-full justify-center">
         {dataOps.map((item, index) => (
           <div key={item.title} data-tip={item.description} className="tooltip tooltip-top m-0 p-0">
@@ -85,8 +86,8 @@ export const QuerySidePanel = (props: {
           </div>
         ))}
       </div>
-      <div className="overflow-x-hidden flex-shrink flex-grow-0 w-full">
-        <ul className="menu bg-base-200 p-0 [&_li>*]:rounded-none h-full w-full">
+      <div className="overflow-x-hidden bg-base-200 h-full w-full mt-1">
+        <ul className="menu bg-base-200 p-0 [&_li>*]:rounded-none w-full pb-10">
           {Object.values(AllLogicMap)
             .filter((item) => !filter || item.key.toLowerCase().includes(filter === 'number' ? 'float' : 'string'))
             .filter((item) => selectedOp === -1 || item.key.toLowerCase().includes(dataOps?.[selectedOp].title))
@@ -97,9 +98,9 @@ export const QuerySidePanel = (props: {
                   data-tip={item.description}
                   className="flex before:w-[10rem] before:text-center tooltip tooltip-bottom text-start "
                   onDragStart={(e) => onDragStart(e, item)}
-                  draggable={props.draggable}
+                  draggable={true}
                   onClick={() => {
-                    if (!props.draggable && props?.onClick) props.onClick(item);
+                    props.onClick(item);
                   }}
                 >
                   {item.icon && (
@@ -123,7 +124,3 @@ export const QuerySidePanel = (props: {
     </div>
   );
 };
-
-export type QueryBuilderProps = {
-  onRunQuery?: () => void;
-};
diff --git a/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderMLPanel.tsx b/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderMLPanel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5ba2dfec5fd9efe754c3d212f430ab973e341aa3
--- /dev/null
+++ b/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderMLPanel.tsx
@@ -0,0 +1,82 @@
+import { useAppDispatch, useML } from '@graphpolaris/shared/lib/data-access';
+import {
+  setCentralityEnabled,
+  setCommunityDetectionEnabled,
+  setLinkPredictionEnabled,
+  setShortestPathEnabled,
+} from '@graphpolaris/shared/lib/data-access/store/mlSlice';
+
+export const QueryBuilderMLPanel = () => {
+  const dispatch = useAppDispatch();
+  const ml = useML();
+
+  return (
+    <div className="card w-max">
+      <div className="card-body p-5">
+        <h2>Machine Learning Options</h2>
+        <div className="form-control">
+          <label className="label cursor-pointer gap-2 w-fit">
+            <input
+              type="checkbox"
+              checked={ml.linkPrediction.enabled}
+              className="checkbox checkbox-sm"
+              onChange={(e) => dispatch(setLinkPredictionEnabled(e.target.checked))}
+            />
+            <span className="label-text">Link Prediction</span>
+          </label>
+          {ml.linkPrediction.enabled && ml.linkPrediction.result && <span># of predictions: {ml.linkPrediction.result.length}</span>}
+          {ml.linkPrediction.enabled && !ml.linkPrediction.result && <span>Loading...</span>}
+        </div>
+        <div className="form-control">
+          <label className="label cursor-pointer gap-2 w-fit">
+            <input
+              type="checkbox"
+              checked={ml.centrality.enabled}
+              className="checkbox checkbox-sm"
+              onChange={(e) => dispatch(setCentralityEnabled(e.target.checked))}
+            />
+            <span className="label-text">Centrality</span>
+          </label>
+          {ml.centrality.enabled && Object.values(ml.centrality.result).length > 0 && (
+            <span>
+              sum of centers:
+              {Object.values(ml.centrality.result)
+                .reduce((a, b) => b + a)
+                .toFixed(2)}
+            </span>
+          )}
+          {ml.centrality.enabled && Object.values(ml.centrality.result).length === 0 && <span>No Centers Found</span>}
+        </div>
+        <div className="form-control">
+          <label className="label cursor-pointer gap-2 w-fit">
+            <input
+              type="checkbox"
+              checked={ml.communityDetection.enabled}
+              className="checkbox checkbox-sm"
+              onChange={(e) => dispatch(setCommunityDetectionEnabled(e.target.checked))}
+            />
+            <span className="label-text">Community Detection</span>
+          </label>
+          {ml.communityDetection.enabled && ml.communityDetection.result && (
+            <span># of communities: {ml.communityDetection.result.length}</span>
+          )}
+          {ml.communityDetection.enabled && !ml.communityDetection.result && <span>Loading...</span>}
+        </div>
+        <div className="form-control">
+          <label className="label cursor-pointer gap-2 w-fit">
+            <input
+              type="checkbox"
+              checked={ml.shortestPath.enabled}
+              className="checkbox checkbox-sm"
+              onChange={(e) => dispatch(setShortestPathEnabled(e.target.checked))}
+            />
+            <span className="label-text">Shortest Path</span>
+          </label>
+          {ml.shortestPath.enabled && ml.shortestPath.result?.length > 0 && <span># of hops: {ml.shortestPath.result.length}</span>}
+          {ml.shortestPath.enabled && !ml.shortestPath.srcNode && <span>Please select source node</span>}
+          {ml.shortestPath.enabled && ml.shortestPath.srcNode && !ml.shortestPath.trtNode && <span>Please select target node</span>}
+        </div>
+      </div>
+    </div>
+  );
+};
diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts
index c82c1f65581f799e376a79be6cad5f4cb8383c3e..3648e10babc7ef4889fc0bef4358d0a877c5a6bb 100644
--- a/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts
+++ b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts
@@ -763,6 +763,7 @@ describe('QueryUtils with Logic', () => {
     const expected: BackendQueryFormat = {
       ...defaultQuery,
       logic: ['==', '@e1.age', 0],
+      machineLearning: [],
       query: [
         {
           ID: 'path_0',
@@ -830,6 +831,7 @@ describe('QueryUtils with Logic', () => {
     const expected: BackendQueryFormat = {
       ...defaultQuery,
       logic: ['==', ['+', '@e1.age', '@e2.age'], 0],
+      machineLearning: [],
       query: [
         {
           ID: 'path_0',
@@ -893,6 +895,7 @@ describe('QueryUtils with Logic', () => {
     const expected: BackendQueryFormat = {
       ...defaultQuery,
       logic: ['<', ['Avg', '@e1.age'], 0],
+      machineLearning: [],
       query: [
         {
           ID: 'path_0',
@@ -941,6 +944,7 @@ describe('QueryUtils with Logic', () => {
     const expected: BackendQueryFormat = {
       ...defaultQuery,
       logic: ['<', '@e1.age', 5],
+      machineLearning: [],
       query: [
         {
           ID: 'path_0',
@@ -958,3 +962,77 @@ describe('QueryUtils with Logic', () => {
     expect(ret).toMatchObject(expected);
   });
 });
+
+it('should no connections between entities and relations', () => {
+  const graph = new QueryMultiGraphology();
+
+  const e1 = graph.addPill2Graphology(
+    {
+      id: 'e1',
+      type: QueryElementTypes.Entity,
+      x: 100,
+      y: 100,
+      name: 'Airport 1',
+    },
+    [{ name: 'age', type: 'string' }]
+  );
+
+  const e2 = graph.addPill2Graphology(
+    {
+      id: 'e2',
+      type: QueryElementTypes.Entity,
+      x: 100,
+      y: 100,
+      name: 'Airport 2',
+    },
+    [{ name: 'age', type: 'string' }]
+  );
+  const r1 = graph.addPill2Graphology(
+    {
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 100,
+      y: 100,
+      name: 'Relation 1',
+    },
+    [{ name: 'age', type: 'string' }]
+  );
+
+  const expected: BackendQueryFormat = {
+    ...defaultQuery,
+    logic: undefined,
+    machineLearning: [],
+    query: [
+      {
+        ID: 'path_0',
+        node: {
+          ID: 'e1',
+          label: 'Airport 1',
+          relation: undefined,
+        },
+      },
+      {
+        ID: 'path_1',
+        node: {
+          ID: 'e2',
+          label: 'Airport 2',
+          relation: undefined,
+        },
+      },
+      {
+        ID: 'path_2',
+        node: {
+          relation: {
+            ID: 'r1',
+            label: 'Relation 1',
+            direction: 'TO',
+            node: {},
+          },
+        },
+      },
+    ],
+  };
+
+  let ret = Query2BackendQuery('database', graph.export());
+  expect(ret).toMatchObject(expected);
+});
diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.ts
index 50245ef1df77c10ba3e7ed5ec6a3f8b59132ae09..9e24d3f55a466cfd0da7b932ef082f7fa8bced3a 100644
--- a/libs/shared/lib/querybuilder/query-utils/query2backend.ts
+++ b/libs/shared/lib/querybuilder/query-utils/query2backend.ts
@@ -1,6 +1,6 @@
 import { EntityNodeAttributes, LogicNodeAttributes, QueryGraphNodes, RelationNodeAttributes } from '../model/graphology/model';
 import { QueryMultiGraph } from '../model/graphology/utils';
-import { BackendQueryFormat, NodeStruct, QueryStruct, RelationStruct } from '../model/BackendQueryFormat';
+import { BackendQueryFormat, MachineLearning, NodeStruct, QueryStruct, RelationStruct } from '../model/BackendQueryFormat';
 import { Handles, QueryElementTypes, toHandleData } from '../model';
 import { get } from 'http';
 import { SerializedEdge, SerializedNode } from 'graphology-types';
@@ -9,6 +9,7 @@ import { hasCycle } from 'graphology-dag';
 import Graph, { MultiGraph } from 'graphology';
 import { allSimplePaths } from 'graphology-simple-path';
 import { AllLogicStatement, ReferenceStatement } from '../model/logic/general';
+import { ML, MLTypes, mlDefaultState } from '../../data-access/store/mlSlice';
 
 // export type QueryI {
 
@@ -162,15 +163,23 @@ function queryLogicUnion(graphLogicChunks: AllLogicStatement[]): AllLogicStateme
 export function Query2BackendQuery(
   databaseName: string,
   graph: QueryMultiGraph,
+  ml: ML = mlDefaultState,
   options: Query2BackendQueryOptions = {}
 ): BackendQueryFormat {
   let query: BackendQueryFormat = {
     databaseName: databaseName,
     query: [],
+    machineLearning: [],
     limit: options.limit || 500,
     return: ['*'], // TODO
   };
 
+  Object.keys(ml).forEach((mlType) => {
+    if (ml[mlType as MLTypes].enabled) {
+      query.machineLearning.push({ type: mlType as MLTypes });
+    }
+  });
+
   let entities = graph.nodes.filter((n) => n?.attributes?.type === QueryElementTypes.Entity) as SerializedNode<EntityNodeAttributes>[];
   let relations = graph.nodes.filter((n) => n?.attributes?.type === QueryElementTypes.Relation) as SerializedNode<RelationNodeAttributes>[];
 
@@ -210,10 +219,10 @@ export function Query2BackendQuery(
     // start with all entities that have no left handle, which means it "starts" a logic
     chunkOffset += traverseEntityRelationPaths(entity, graphSequenceChunks, i + chunkOffset, graph, entities, relations);
   });
-  if (entitiesEmptyLeftHandle.length > 0) chunkOffset++;
-  relationsEmptyLeftHandle.map((entity, i) => {
+  if (entitiesEmptyLeftHandle.length > 0) chunkOffset += entitiesEmptyLeftHandle.length;
+  relationsEmptyLeftHandle.map((relation, i) => {
     // then, for all relations that have no left handle, since they weren't accounted by the loop above
-    chunkOffset += traverseEntityRelationPaths(entity, graphSequenceChunks, i + chunkOffset, graph, entities, relations);
+    chunkOffset += traverseEntityRelationPaths(relation, graphSequenceChunks, i + chunkOffset, graph, entities, relations);
   });
   graphSequenceChunks.forEach((chunkSequence, i) => {
     chunkSequence.forEach((chunk, j) => {
@@ -236,6 +245,8 @@ export function Query2BackendQuery(
   // Logic pathways extraction: now traverse the graph again though the logic components to construct the logic chunks
 
   // console.log('logics', logics);
+  // console.log('entitiesEmptyLeftHandle', entitiesEmptyLeftHandle);
+  // console.log('relationsEmptyLeftHandle', relationsEmptyLeftHandle);
   // console.log('graphSequenceChunks', graphSequenceChunks);
   // console.log('graphLogicChunks', graphLogicChunks);
   // console.log('logicsRightHandleConnectedOutside', logicsRightHandleConnectedOutside);
diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx
index def690e907db2e986a0a7af772863c92bd2a031f..ac1f856e87c379db3bafba5d36d9731277cef2be 100644
--- a/libs/shared/lib/schema/panel/schema.tsx
+++ b/libs/shared/lib/schema/panel/schema.tsx
@@ -98,7 +98,6 @@ export const Schema = (props: Props) => {
     updateLayout();
     const expandedSchema = schemaExpandRelation(schemaGraphology);
     layout.current?.layout(expandedSchema);
-    console.log(expandedSchema);
 
     const schemaFlow = schemaGraphology2Reactflow(expandedSchema);
 
@@ -129,7 +128,6 @@ export const Schema = (props: Props) => {
   // console.log(nodes, edges);
 
   useEffect(() => {
-    console.log(settingsIconRef.current?.getBoundingClientRect());
     if (dialogRef.current && settingsIconRef.current) {
       dialogRef.current.style.top = `${settingsIconRef.current.getBoundingClientRect().top}px`;
       dialogRef.current.style.left = `${settingsIconRef.current.getBoundingClientRect().left + 30}px`;
diff --git a/libs/shared/lib/schema/panel/schemaDialog.tsx b/libs/shared/lib/schema/panel/schemaDialog.tsx
index e2bb5ffd846143403d1c0de8ee2c0866bca16d92..1784d946058f05f47bba634d9e1b6af8930c6ee2 100644
--- a/libs/shared/lib/schema/panel/schemaDialog.tsx
+++ b/libs/shared/lib/schema/panel/schemaDialog.tsx
@@ -7,7 +7,7 @@ export const SchemaDialog = React.forwardRef<HTMLDivElement, DialogProps>((props
   return (
     <>
       {props.open && (
-        <div className="absolute top-0 left-10 opacity-100 transition-opacity group-hover:opacity-100 z-50 " ref={ref}>
+        <div className="absolute  opacity-100 transition-opacity group-hover:opacity-100 z-50 " ref={ref}>
           <div className="card absolute card-bordered bg-white rounded-none">
             <form
               className="card-body px-0 w-72 py-5"
@@ -24,15 +24,15 @@ export const SchemaDialog = React.forwardRef<HTMLDivElement, DialogProps>((props
               <div className="divider m-0"></div>
               <div className="form-control px-5">
                 <label className="label cursor-pointer w-fit gap-2 px-0 py-1">
-                  <input type="checkbox" checked={true} className="checkbox checkbox-xs" />
+                  <input type="checkbox" checked={true} onChange={(e) => {}} className="checkbox checkbox-xs" />
                   <span className="label-text">Points</span>
                 </label>
                 <label className="label cursor-pointer w-fit gap-2 px-0 py-1">
-                  <input type="checkbox" checked={true} className="checkbox checkbox-xs" />
+                  <input type="checkbox" checked={true} onChange={(e) => {}} className="checkbox checkbox-xs" />
                   <span className="label-text">Line</span>
                 </label>
                 <label className="label cursor-pointer w-fit gap-2 px-0 py-1">
-                  <input type="checkbox" checked={true} className="checkbox checkbox-xs" />
+                  <input type="checkbox" checked={true} onChange={(e) => {}} className="checkbox checkbox-xs" />
                   <span className="label-text">Line</span>
                 </label>
               </div>
@@ -41,7 +41,7 @@ export const SchemaDialog = React.forwardRef<HTMLDivElement, DialogProps>((props
                 <label className="label">
                   <span className="label-text">Opacity</span>
                 </label>
-                <input type="range" min={0} max="100" value="40" className="range range-sm" />
+                <input type="range" min={0} max="100" value="40" onChange={(e) => {}} className="range range-sm" />
               </div>
               <div className="divider m-0"></div>
               <div className="form-control px-5">
diff --git a/libs/shared/lib/vis/nodelink/Types.tsx b/libs/shared/lib/vis/nodelink/Types.tsx
index 8df3a97da2430966c7940f46ec997aecf017bc30..4103fa516745ce12b044603549e0b17d6ff89220 100644
--- a/libs/shared/lib/vis/nodelink/Types.tsx
+++ b/libs/shared/lib/vis/nodelink/Types.tsx
@@ -10,10 +10,10 @@ import * as PIXI from 'pixi.js';
 export type GraphType = {
   nodes: NodeType[];
   links: LinkType[];
-  linkPrediction: boolean;
-  shortestPath: boolean;
-  communityDetection: boolean;
-  numberOfMlClusters?: number;
+  // linkPrediction?: boolean;
+  // shortestPath?: boolean;
+  // communityDetection?: boolean;
+  // numberOfMlClusters?: number;
 };
 
 /** The interface for a node in the node-link diagram */
@@ -34,6 +34,8 @@ export interface NodeType extends d3.SimulationNodeDatum {
   gfxtext?: PIXI.Text;
   gfxAttributes?: PIXI.Graphics;
   selected?: boolean;
+  isShortestPathSource?: boolean;
+  isShortestPathTarget?: boolean;
   index?: number;
 
   // The text that will be shown on top of the node if selected.
@@ -61,9 +63,12 @@ export interface NodeType extends d3.SimulationNodeDatum {
 /** The interface for a link in the node-link diagram */
 export interface LinkType extends d3.SimulationLinkDatum<NodeType> {
   // The thickness of a line
+  id: string;
   value: number;
   // To check if an edge is calculated based on a ML algorithm
   mlEdge: boolean;
+  color: number;
+  alpha?: number;
 }
 
 /**collectionNode holds 1 entry per node kind (so for example a MockNode with name "parties" and all associated attributes,) */
diff --git a/libs/shared/lib/vis/nodelink/components/NLForce.tsx b/libs/shared/lib/vis/nodelink/components/NLForce.tsx
index 54bdd14f7a0efa419993276651d2d8e04ebcf8c5..567e82cfbe5339967c836df519ff0bf9191cbca3 100644
--- a/libs/shared/lib/vis/nodelink/components/NLForce.tsx
+++ b/libs/shared/lib/vis/nodelink/components/NLForce.tsx
@@ -7,33 +7,20 @@ export const simulation = forceSimulation<NodeType, LinkType>();
 /** StartSimulation starts the d3 forcelink simulation. This has to be called after the simulation is initialized. */
 export function startSimulation(graph: GraphType, windowSize: { width: number; height: number }): void {
   simulation
-    .alpha(1.8)
+    .alpha(2)
+    .alphaDecay(0.02)
     .force(
       'link',
-      forceLink()
+      forceLink<NodeType, LinkType>()
         .id((d: any) => d.id)
-        .strength(1)
+        .strength((link, i, links) => (link.mlEdge ? 0.001 : 1))
         .distance(25)
     )
-    .force('charge', forceManyBody().strength(-1))
-    .force('center', forceCenter(windowSize.width / 2, windowSize.height / 2).strength(0.5))
-    .force('collide', forceCollide().strength(1).radius(5).iterations(1)) // Force that avoids circle overlapping
-    .force('attract', forceRadial(50, windowSize.width / 2, windowSize.height / 2).strength(0.005));
+    .force('charge', forceManyBody().strength(-4).distanceMax(200).distanceMin(0))
+    .force('center', forceCenter(windowSize.width / 2, windowSize.height / 2).strength(0.02))
+    .force('collide', forceCollide().strength(0.2).radius(15).iterations(1)) // Force that avoids circle overlapping
+    .force('attract', forceRadial(50, windowSize.width / 2, windowSize.height / 2).strength(0.002));
   simulation.nodes(graph.nodes);
   simulation.force<d3.ForceLink<NodeType, LinkType>>('link')?.links(graph.links);
   simulation.alphaTarget(0).restart();
 }
-
-// export const useNLForce = ({ graph, windowSize }: Props) => {
-//   useEffect(() => {
-//     if (graph && graph.nodes.length > 0 && graph.links.length > 0 && windowSize.width && windowSize.height) {
-//       startSimulation(graph);
-//     }
-//   }, [graph, windowSize.width, windowSize.height]);
-
-//   function refresh(): void {
-//     simulation.restart();
-//   }
-
-//   return { simulation, refresh, startSimulation };
-// };
diff --git a/libs/shared/lib/vis/nodelink/components/NLMachineLearning.tsx b/libs/shared/lib/vis/nodelink/components/NLMachineLearning.tsx
index a06ebc311d45fdbe3a00d7d02bd52b207303ac96..f6f46d863c1d2bdabf304f6e4b4929bee4d701c3 100644
--- a/libs/shared/lib/vis/nodelink/components/NLMachineLearning.tsx
+++ b/libs/shared/lib/vis/nodelink/components/NLMachineLearning.tsx
@@ -2,6 +2,63 @@ import { useState } from 'react';
 import { AttributeData, NodeAttributeData } from '../../shared/InputDataTypes';
 import { AttributeCategory } from '../../shared/Types';
 import { GraphType, LinkType, NodeType } from '../Types';
+import { ML } from '@graphpolaris/shared/lib/data-access/store/mlSlice';
+
+export function processLinkPrediction(ml: ML, graph: GraphType): GraphType {
+  if (ml === undefined || ml.linkPrediction === undefined) return graph;
+
+  if (ml.linkPrediction.enabled) {
+    let allNodeIds = new Set(graph.nodes.map((n) => n.id));
+    ml.linkPrediction.result.forEach((link) => {
+      if (allNodeIds.has(link.from) && allNodeIds.has(link.to)) {
+        const toAdd: LinkType = {
+          id: link.from + link.to,
+          source: link.from,
+          target: link.to,
+          value: link.attributes.jaccard_coefficient as number,
+          mlEdge: true,
+          color: 0x000000,
+        };
+        graph.links.push(toAdd);
+      }
+    });
+  }
+  return graph;
+}
+
+export function processCommunityDetection(ml: ML, graph: GraphType): GraphType {
+  if (ml === undefined || ml.communityDetection === undefined) return graph;
+
+  if (ml.communityDetection.enabled) {
+    let allNodeIdMap = new Map<string, number>();
+    ml.communityDetection.result.forEach((idSet, i) => {
+      idSet.forEach((id) => {
+        allNodeIdMap.set(id, i);
+      });
+    });
+
+    graph.nodes = graph.nodes.map((node, i) => {
+      if (allNodeIdMap.has(node.id)) {
+        node.cluster = allNodeIdMap.get(node.id);
+      } else {
+        node.cluster = -1;
+      }
+      return node;
+    });
+  } else {
+    graph.nodes = graph.nodes.map((node, i) => {
+      node.cluster = undefined;
+      return node;
+    });
+  }
+  return graph;
+}
+
+export function processML(ml: ML, graph: GraphType): GraphType {
+  let ret = processLinkPrediction(ml, graph);
+  ret = processCommunityDetection(ml, ret);
+  return ret;
+}
 
 export const useNLMachineLearning = (props: {
   graph: GraphType;
@@ -114,7 +171,7 @@ export const useNLMachineLearning = (props: {
 
   /**
    * resetClusterOfNodes is a function that resets the cluster of the nodes that are being customised by the user,
-   * after a community detection algorithm, where the cluster of these node could have been changed.
+   * after a community detection algorithm, where the cluster of these nodes could have been changed.
    */
   const resetClusterOfNodes = (type: number): void => {
     props.graph.nodes.forEach((node: NodeType) => {
diff --git a/libs/shared/lib/vis/nodelink/components/NLPixi.tsx b/libs/shared/lib/vis/nodelink/components/NLPixi.tsx
index 3ff52dc93ec380912d2a6ef8277e3c4124006d11..3f236696c4868bbac939348aa05114e2185bbdac 100644
--- a/libs/shared/lib/vis/nodelink/components/NLPixi.tsx
+++ b/libs/shared/lib/vis/nodelink/components/NLPixi.tsx
@@ -1,22 +1,22 @@
 import { GraphType, LinkType, NodeType } from '../Types';
 import { tailwindColors } from 'config';
-import { useEffect, useMemo, useRef, useState } from 'react';
-import { Application, Circle, Container, FederatedPointerEvent, Graphics, Point, Sprite, Texture, autoDetectRenderer } from 'pixi.js';
+import { ReactEventHandler, useEffect, useMemo, useRef, useState } from 'react';
+import { Application, Circle, Container, FederatedPointerEvent, Graphics, IPointData } from 'pixi.js';
 import { binaryColor, nodeColor as nodeColor } from './utils';
 import { select, zoom as d3zoom, drag as d3drag } from 'd3';
 import * as force from './NLForce';
 import { Viewport } from 'pixi-viewport';
-import ResultNodeLinkParserUseCase from '../../shared/ResultNodeLinkParserUseCase';
 import { GraphQueryResult, GraphQueryResultFromBackendPayload } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
+import { useAppDispatch, useML } from '@graphpolaris/shared/lib/data-access';
+import { ML, setShortestPathSource, setShortestPathTarget } from '@graphpolaris/shared/lib/data-access/store/mlSlice';
+import { parseQueryResult } from './query2NL';
 
 type Props = {
-  windowSize: { width: number; height: number };
   onClick: (node: NodeType) => void;
   highlightNodes: NodeType[];
   currentShortestPathEdges?: LinkType[];
   highlightedLinks?: LinkType[];
-  jaccardThreshold?: number;
-  myDiv: HTMLDivElement | null;
+  graph?: GraphType;
 };
 
 const app = new Application({ background: 0xffffff, antialias: true, autoDensity: true, eventMode: 'auto' });
@@ -27,44 +27,40 @@ const links = new Container();
 // MAIN COMPONENT
 //////////////////
 
-export const useNLPixi = (props: Props) => {
-  const [drag, setDrag] = useState({ x: 0, y: 0 });
+export const NLPixi = (props: Props) => {
   const nodeMap = useRef(new Map<string, Graphics>());
   const linkMap = useRef(new Map<string, Graphics>());
-  const graph = useRef<GraphType>();
   const viewport = useRef<Viewport>();
+  const ref = useRef<HTMLDivElement>(null);
+  const isSetup = useRef(false);
+  const ml = useML();
+  const dragging = useRef<{ node: NodeType; gfx: Graphics } | null>(null);
+  const onlyClicked = useRef(false);
+  const dispatch = useAppDispatch();
 
-  useEffect(() => {
-    app.renderer.resize(props.windowSize.width, props.windowSize.height);
-    app.render();
-  }, [props.windowSize]);
+  // useEffect(() => {
+  //   app.renderer.resize(props.windowSize.width, props.windowSize.height);
+  //   app.render();
+  // }, [props.windowSize]);
 
   useEffect(() => {
-    if (props.myDiv && props.myDiv.children.length === 0) props.myDiv.appendChild(app.view as HTMLCanvasElement);
-  }, [props.myDiv]);
-
-  const updateNode = (node: NodeType) => {
-    // console.log(item);
-    const gfx = nodeMap.current.get(node.id);
-    gfx?.position.set(node.x, node.y);
+    if (!ref.current) return;
+    const resizeObserver = new ResizeObserver(() => {
+      app.renderer.resize(ref?.current?.clientWidth || 1000, ref?.current?.clientHeight || 1000);
+      app.render();
+    });
+    resizeObserver.observe(ref.current);
+    return () => resizeObserver.disconnect(); // clean up
+  }, []);
 
-    // if (!item.position) {
-    //   item.position = new Point(node.x, node.y);
-    // } else {
-    //   item.position.set(node.x, node.y);
-    // }
-    // Update attributes position if they exist
-    // if (node.gfxAttributes) {
-    //   const x = node.x - node.gfxAttributes.width / 2;
-    //   const y = node.y - node.gfxAttributes.height - 20;
-    //   if (!node.gfxAttributes?.position) node.gfxAttributes.position = new Point(x, y);
-    //   else {
-    //     node.gfxAttributes.position.set(x, y);
-    //   }
-    // }
-  };
+  useEffect(() => {
+    if (ref.current && ref.current.children.length === 0) {
+      ref.current.appendChild(app.view as HTMLCanvasElement);
+      app.renderer.resize(ref?.current?.clientWidth || 1000, ref?.current?.clientHeight || 1000);
+      app.render();
+    }
+  }, [ref]);
 
-  const dragging = useRef<{ node: NodeType; gfx: Graphics } | null>(null);
   function onDragStart(event: FederatedPointerEvent, node: NodeType, gfx: Graphics) {
     // store a reference to the data
     // the reason for this is because of multitouch
@@ -72,6 +68,7 @@ export const useNLPixi = (props: Props) => {
     event.stopPropagation();
     if (viewport.current) viewport.current.pause = true;
     dragging.current = { node, gfx };
+    onlyClicked.current = true;
   }
 
   function onDragEnd(event: FederatedPointerEvent) {
@@ -79,15 +76,19 @@ export const useNLPixi = (props: Props) => {
       event.stopPropagation();
       dragging.current.node.fx = null;
       dragging.current.node.fy = null;
-      dragging.current = null;
       if (viewport.current) viewport.current.pause = false;
+      if (onlyClicked.current) {
+        onlyClicked.current = false;
+        props.onClick(dragging.current.node);
+      }
+      dragging.current = null;
     }
   }
 
   function onDragMove(event: FederatedPointerEvent) {
     if (dragging.current) {
+      onlyClicked.current = false;
       event.stopPropagation();
-      console.log(viewport.current?.scaled);
 
       if (!dragging.current.node.fx) dragging.current.node.fx = dragging.current.node.x || 0;
       if (!dragging.current.node.fy) dragging.current.node.fy = dragging.current.node.y || 0;
@@ -97,51 +98,82 @@ export const useNLPixi = (props: Props) => {
     }
   }
 
+  const updateNode = (node: NodeType) => {
+    const gfx = nodeMap.current.get(node.id);
+    if (!gfx) return;
+
+    const lineColor = node.isShortestPathSource
+      ? tailwindColors.entity[950]
+      : node.isShortestPathTarget
+      ? tailwindColors.relation[950]
+      : node.selected
+      ? tailwindColors.entity[400]
+      : '#000000';
+    const lineWidth = node.selected ? 3 : 1.5;
+    gfx.lineStyle(lineWidth, binaryColor(lineColor));
+
+    if (node?.cluster) {
+      gfx.beginFill(node.cluster >= 0 ? nodeColor(node.cluster) : 0x000000);
+    } else gfx.beginFill(nodeColor(node.type));
+
+    gfx.drawCircle(0, 0, Math.max(node.radius || 5, 5));
+    gfx.endFill();
+
+    gfx.position.set(node.x, node.y);
+
+    gfx.off('mousedown');
+    gfx.off('mousemove');
+    gfx.off('mouseup');
+    gfx.on('mousedown', (e) => onDragStart(e, node, gfx));
+    gfx.on('mousemove', onDragMove);
+    gfx.on('mouseup', onDragEnd);
+
+    // if (!item.position) {
+    //   item.position = new Point(node.x, node.y);
+    // } else {
+    //   item.position.set(node.x, node.y);
+    // }
+    // Update attributes position if they exist
+    // if (node.gfxAttributes) {
+    //   const x = node.x - node.gfxAttributes.width / 2;
+    //   const y = node.y - node.gfxAttributes.height - 20;
+    //   if (!node.gfxAttributes?.position) node.gfxAttributes.position = new Point(x, y);
+    //   else {
+    //     node.gfxAttributes.position.set(x, y);
+    //   }
+    // }
+  };
+
   const createNode = (node: NodeType, selected?: boolean) => {
     // check if node is already drawn, and if so, delete it
     if (node && node?.id && nodeMap.current?.has(node.id)) {
       nodeMap.current.delete(node.id);
     }
+    // Do not draw node if it has no position
+    if (node.x === undefined || node.y === undefined) return;
     const gfx = new Graphics();
+    nodeMap.current.set(node.id, gfx);
+    nodes.addChild(gfx);
     node.selected = selected;
-    const lineColor = selected ? tailwindColors.entity[400] : '#000000';
 
-    const lineWidth = selected ? 3 : 1.5;
-    gfx.lineStyle(lineWidth, binaryColor(lineColor));
-    //check if not undefined.
-    if (node.cluster) {
-      gfx.beginFill(nodeColor(node.cluster));
-    } else {
-      //if cluster is undefined, use type to determine the color.
-      //check if not undefined.
-      if (node.type) {
-        gfx.beginFill(nodeColor(node.type));
-      }
-    }
-    gfx.drawCircle(0, 0, Math.max(node.radius || 5, 5));
+    updateNode(node);
     gfx.hitArea = new Circle(0, 0, 4);
     gfx.name = 'node_' + node.id;
-    gfx.position.set(node.x, node.y);
     gfx.eventMode = 'dynamic';
-    gfx.on('mousedown', (e) => onDragStart(e, node, gfx));
-    gfx.on('mousemove', onDragMove);
-    gfx.on('mouseup', onDragEnd);
 
-    nodes.addChild(gfx);
-    nodeMap.current.set(node.id, gfx);
     return gfx;
   };
 
-  /** UpdateRadius works just like UpdateColors, but also applies radius*/
-  const UpdateRadius = (graph: GraphType, radius: number) => {
-    // update for each node in graph
-    graph.nodes.forEach((node: NodeType) => {
-      createNode(node);
-    });
-  };
+  // /** UpdateRadius works just like UpdateColors, but also applies radius*/
+  // const UpdateRadius = (graph: GraphType, radius: number) => {
+  //   // update for each node in graph
+  //   graph.nodes.forEach((node: NodeType) => {
+  //     createNode(node);
+  //   });
+  // };
 
-  const updateLink = (link: LinkType) => {
-    if (!graph.current) return;
+  const updateLink = (link: LinkType, gfx: Graphics) => {
+    if (!props.graph) return;
 
     const _source = link.source;
     const _target = link.target;
@@ -168,88 +200,180 @@ export const useNLPixi = (props: Props) => {
       targetId = target.id;
     }
     if (!source || !target) {
-      console.log('source or target not found', source, target, sourceId, targetId);
+      console.error('source or target not found', source, target, sourceId, targetId);
       return;
     }
 
-    const id = sourceId + targetId;
-    const gfx = linkMap.current.get(id);
-
     if (gfx) {
+      let color = link.color || 0x000000;
+      let style = 0.2;
+      let alpha = link.alpha || 1;
+      if (link.mlEdge) {
+        color = 0x0000ff;
+        if (link.value > ml.communityDetection.jaccard_threshold) {
+          style = link.value * 1.8;
+        } else {
+          style = 0;
+          alpha = 0.2;
+        }
+      } else if (props.highlightedLinks && props.highlightedLinks.includes(link)) {
+        if (link.mlEdge && ml.communityDetection.jaccard_threshold) {
+          if (link.value > ml.communityDetection.jaccard_threshold) {
+            color = 0xaa00ff;
+            style = link.value * 1.8;
+          }
+        } else {
+          color = 0xff0000;
+          style = 1.0;
+        }
+      } else if (props.currentShortestPathEdges && props.currentShortestPathEdges.includes(link)) {
+        color = 0x00ff00;
+        style = 3.0;
+      }
+
       gfx.clear();
       gfx.beginFill();
-      // Check if link is a machine learning edge
-      //   if (link.mlEdge && props.jaccardThreshold) {
-      //     if (link.value > props.jaccardThreshold) {
-      //       gfx.moveTo(source.x, source.y);
-      //       gfx.lineTo(target.x, target.y);
-      //       gfx.lineStyle(link.value * 1.8, 0x0000ff);
-      //     }
-      //   } else gfx.lineStyle(0.2, 0x000000);
-      //   //Check if link is hightlighted
-      //   if (props.highlightedLinks && props.highlightedLinks.includes(link)) {
-      //     //If highlighted and ML edge make it purple
-      //     if (link.mlEdge && props.jaccardThreshold) {
-      //       if (link.value > props.jaccardThreshold) {
-      //         gfx.lineStyle(link.value * 1.8, 0xaa00ff);
-      //       }
-      //       //If highlight and not ML edge make it red
-      //     } else {
-      //       gfx.lineStyle(1.0, 0xff0000);
-      //     }
-      //   }
-      //   if (props.currentShortestPathEdges && props.currentShortestPathEdges.includes(link)) {
-      //     gfx.lineStyle(3.0, 0x00ff00);
-      //   }
-      //   console.log('link', source.x);
-
-      //   gfx.position.set(source.x, source.y);
-      //   gfx.moveTo(source.x || 0, source.y || 0);
-      //   gfx.lineTo(target.x || 1000, target.y || 1000);
-      //   gfx.position.set(source.x, source.y);
       gfx
-        .lineStyle(0.5, 0x000000)
+        .lineStyle(style, color, alpha)
         .moveTo(source.x || 0, source.y || 0)
         .lineTo(target.x || 0, target.y || 0);
       gfx.endFill();
+    } else {
+      throw Error('Link not found');
     }
   };
 
   const createLink = (link: LinkType) => {
-    const sourceId = link.source as string;
-    const targetId = link.target as string;
-    const id = sourceId + targetId;
-
-    if (linkMap.current.has(id)) {
-      linkMap.current.delete(id);
+    if (linkMap.current.has(link.id)) {
+      linkMap.current.delete(link.id);
     }
     const gfx = new Graphics();
-    gfx.name = 'link_' + id;
-    linkMap.current.set(id, gfx);
-    updateLink(link);
+    gfx.name = 'link_' + link.id;
+    linkMap.current.set(link.id, gfx);
+    updateLink(link, gfx);
     links.addChild(gfx);
     return gfx;
   };
 
+  useEffect(() => {
+    if (props.graph && ref.current && ref.current.children.length > 0) {
+      if (!isSetup.current) setup();
+      else update();
+      // setup();
+    }
+  }, [props.graph]);
+
+  const tick = (delta: number) => {
+    if (props.graph) {
+      props.graph.nodes.forEach((node: NodeType) => {
+        const gfx = nodeMap.current.get(node.id);
+        if (!gfx || node.x === undefined || node.y === undefined) return;
+        gfx.position.copyFrom(node as IPointData);
+        // const pos = gfx.position;
+        // let normal = { x: node.x - pos.x, y: node.y - pos.y };
+        // const size = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
+        // normal = { x: normal.x / size, y: normal.y / size };
+        // // const mid: IPointData = { x: (pos.x + (node.x || 0)) / 2, y: (pos.y + (node.y || 0)) / 2 };
+        // const vel = { x: pos.x + normal.x * delta * 0.1, y: pos.y + normal.y * delta * 0.1 };
+        // gfx.position.copyFrom(vel as IPointData);
+        // console.log(normal);
+
+        // gfx.position.set(node.x, node.y);
+        // if (node.x - pos.x + (node.y - pos.y) > 10000) gfx.position.set(node.x, node.y);
+        // else {
+        //   const normal = { x: (node.x - pos.x) / (node.x + pos.x), y: (node.y - pos.y) / (node.y + pos.y) };
+        //   gfx.position.set(pos.x + (normal.x / delta) * 50, pos.y + (normal.y / delta) * 50);
+        // }
+      });
+
+      // Update forces of the links
+      props.graph.links.forEach((link: any) => {
+        if (linkMap.current && !!linkMap.current.has(link.id)) updateLink(link, linkMap.current.get(link.id) as Graphics);
+      });
+    }
+  };
+
+  const update = (forceClear = false) => {
+    if (!props.graph || !ref.current) return;
+
+    if (props.graph) {
+      if (forceClear) {
+        nodeMap.current.clear();
+        linkMap.current.clear();
+        nodes.removeChildren();
+        links.removeChildren();
+      }
+
+      nodeMap.current.forEach((gfx, id) => {
+        if (!props.graph?.nodes?.find((node) => node.id === id)) {
+          nodes.removeChild(gfx);
+          gfx.destroy();
+          nodeMap.current.delete(id);
+        }
+      });
+
+      linkMap.current.forEach((gfx, id) => {
+        if (!props.graph?.links?.find((link) => link.id === id)) {
+          links.removeChild(gfx);
+          gfx.destroy();
+          linkMap.current.delete(id);
+        }
+      });
+
+      props.graph.nodes.forEach((node: NodeType) => {
+        if (!forceClear && nodeMap.current.has(node.id)) {
+          const old = nodeMap.current.get(node.id);
+          node.x = old?.x || node.x;
+          node.y = old?.y || node.y;
+          updateNode(node);
+        } else {
+          createNode(node);
+        }
+      });
+      props.graph.links.forEach((link: LinkType) => {
+        if (!forceClear && linkMap.current.has(link.id)) {
+          const gfx = linkMap.current.get(link.id);
+          if (gfx) updateLink(link, gfx);
+        } else createLink(link);
+      });
+
+      // // update text colour (written after nodes so that text appears on top of nodes)
+      //   nodes.forEach((node: NodeType) => {
+      //   if (node.gfxAttributes !== undefined) {
+      //       const selected = node.selected === true;
+      //       node.gfxAttributes.destroy();
+      //       createAttributes(node);
+      //       if (selected) {
+      //       showAttributes(node);
+      //       }
+      //   }
+      //   });
+      // console.log(nodes.children);
+
+      force.startSimulation(props.graph, ref.current.getBoundingClientRect());
+      force.simulation.on('tick', () => {});
+      app.ticker.add(tick);
+    }
+  };
+
   /**
    * SetNodeGraphics is an initializing function. It attaches the nodes and links to the simulation.
    * It creates graphic objects and adds these to the PIXI containers. It also clears both of these of previous nodes and links.
    * @param graph The graph returned from the database and that is parsed into a nodelist and edgelist.
-   * @param radius The radius of the node.
    */
-  const refresh = (graphQueryResult: GraphQueryResult) => {
-    const resultNodeLinkParserUseCase = new ResultNodeLinkParserUseCase();
-    graph.current = resultNodeLinkParserUseCase.parseQueryResult(graphQueryResult);
-
+  const setup = () => {
     nodes.removeChildren();
     links.removeChildren();
     app.stage.removeChildren();
 
+    if (!props.graph) throw Error('Graph is undefined');
+
+    const size = ref.current?.getBoundingClientRect();
     viewport.current = new Viewport({
-      screenWidth: props.windowSize.width,
-      screenHeight: props.windowSize.height,
-      worldWidth: props.windowSize.width,
-      worldHeight: props.windowSize.height,
+      screenWidth: size?.width || 1000,
+      screenHeight: size?.height || 1000,
+      worldWidth: size?.width || 1000,
+      worldHeight: size?.height || 1000,
       stopPropagation: true,
       events: app.renderer.events, // the interaction module is important for wheel to work properly when renderer.view is placed or scaled
     });
@@ -265,68 +389,13 @@ export const useNLPixi = (props: Props) => {
     app.stage.on('mouseup', onDragEnd);
     app.stage.on('pointerup', onDragEnd);
 
-    app.ticker.add(function (delta) {
-      if (graph.current) {
-        graph.current.nodes.forEach((node: any) => {
-          const gfx = nodeMap.current.get(node.id);
-          if (!gfx) return;
-          const pos = gfx.position;
-          //   gfx.position.set(pos.x + (node.x - pos.x) / delta, pos.y + (node.y - pos.y) / delta);
-          gfx.position.set(node.x, node.y);
-        });
-
-        // Update forces of the links
-        graph.current.links.forEach((link: any) => {
-          updateLink(link);
-        });
-      }
-    });
-
-    // app.stage.onmousedown = (e) => {
-    //   isPanning.current = true;
-    //   console.log('mousedown');
-    // };
-    // app.stage.onmousemove = (e) => {
-    //   if (isPanning.current) {
-    //     setPan({ x: pan.x + e.movementX, y: pan.y + e.movementY });
-    //     app.stage.setTransform(pan.x + e.movementX, pan.y + e.movementY, scale, scale);
-    //   }
-    // };
-    // app.stage.onmouseup = (e) => {
-    //   isPanning.current = false;
-    //   console.log('mouseup');
-    // };
-
-    // Create and initialise graphic objects for the nodes
-    if (graph && graph.current.nodes && graph.current.links) {
-      nodeMap.current.clear();
-      graph.current.nodes.forEach((node: NodeType) => {
-        createNode(node);
-      });
-      linkMap.current.clear();
-      graph.current.links.forEach((link: LinkType) => {
-        createLink(link);
-      });
-
-      // // update text colour (written after nodes so that text appears on top of nodes)
-      //   nodes.forEach((node: NodeType) => {
-      //   if (node.gfxAttributes !== undefined) {
-      //       const selected = node.selected === true;
-      //       node.gfxAttributes.destroy();
-      //       createAttributes(node);
-      //       if (selected) {
-      //       showAttributes(node);
-      //       }
-      //   }
-      //   });
-
-      // // refresh
-      //   force.simulation.alphaTarget(0).restart();
+    app.ticker.add(tick);
 
-      force.startSimulation(graph.current, props.windowSize);
-      force.simulation.on('tick', () => {});
-    }
+    nodeMap.current.clear();
+    linkMap.current.clear();
+    update();
+    isSetup.current = true;
   };
 
-  return { renderer: app.renderer, stage: app.stage, links, refresh, graph: graph.current };
+  return <div className="h-full w-full overflow-hidden" ref={ref}></div>;
 };
diff --git a/libs/shared/lib/vis/nodelink/components/query2NL.tsx b/libs/shared/lib/vis/nodelink/components/query2NL.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..57b39f3ba0172100a3f298d595aab53302aa7982
--- /dev/null
+++ b/libs/shared/lib/vis/nodelink/components/query2NL.tsx
@@ -0,0 +1,287 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+import { GraphType, LinkType, NodeType } from '../Types';
+import { Edge, Node, GraphQueryResult } from '../../../data-access/store';
+import { ML } from '../../../data-access/store/mlSlice';
+import { processML } from './NLMachineLearning';
+/** ResultNodeLinkParserUseCase implements methods to parse and translate websocket messages from the backend into a GraphType. */
+
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+/** A node link data-type for a query result object from the backend. */
+// export type NodeLinkResultType = { DEPRECATED USE GraphQueryResult
+//   nodes: Node[];
+//   edges: Link[];
+//   mlEdges?: Link[];
+// };
+
+/** Typing for nodes and links in the node-link result. Nodes and links should always have an id and attributes. */
+// export interface AxisType {
+//   id: string;
+//   attributes: Record<string, any>;
+//   mldata?: Record<string, string[]> | number; // This is shortest path data . This name is needs to be changed together with backend TODO: Change this.
+// }
+
+/** Typing for a node in the node-link result */
+// export type Node = AxisType;
+
+/** Typing for a link in the node-link result */
+// export interface Link extends AxisType {
+//   from: string;
+//   to: string;
+// }
+
+export type AxisType = Node | Edge;
+
+/** Gets the group to which the node/edge belongs */
+export function getGroupName(axisType: AxisType): string {
+  // FIXME: only works in arangodb
+  return axisType.label;
+}
+
+/** Returns true if the given id belongs to the target group. */
+export function isNotInGroup(nodeOrEdge: AxisType, targetGroup: string): boolean {
+  return getGroupName(nodeOrEdge) != targetGroup;
+}
+
+/** Checks if a query result form the backend contains valid NodeLinkResultType data.
+ * @param {any} jsonObject The query result object received from the frontend.
+ * @returns True and the jsonObject will be casted, false if the jsonObject did not contain all the data fields.
+ */
+export function isNodeLinkResult(jsonObject: any): jsonObject is GraphQueryResult {
+  if (typeof jsonObject === 'object' && jsonObject !== null && 'nodes' in jsonObject && 'edges' in jsonObject) {
+    if (!Array.isArray(jsonObject.nodes) || !Array.isArray(jsonObject.edges)) return false;
+
+    const validNodes = jsonObject.nodes.every((node: any) => 'id' in node && 'attributes' in node);
+    const validEdges = jsonObject.edges.every((edge: any) => 'from' in edge && 'to' in edge);
+
+    return validNodes && validEdges;
+  } else return false;
+}
+
+/** Returns a record with a type of the nodes as key and a number that represents how many times this type is present in the nodeLinkResult as value. */
+export function getNodeTypes(nodeLinkResult: GraphQueryResult): Record<string, number> {
+  const types: Record<string, number> = {};
+
+  nodeLinkResult.nodes.forEach((node) => {
+    const type = getGroupName(node);
+    if (types[type] != undefined) types[type]++;
+    else types[type] = 0;
+  });
+
+  return types;
+}
+
+export type UniqueEdge = {
+  from: string;
+  to: string;
+  count: number;
+  attributes: Record<string, any>;
+};
+
+/**
+ * Parse a message (containing query result) edges to unique edges.
+ * @param {Link[]} queryResultEdges Edges from a query result.
+ * @param {boolean} isLinkPredictionData True if parsing LinkPredictionData, false otherwise.
+ * @returns {UniqueEdge[]} Unique edges with a count property added.
+ */
+export function parseToUniqueEdges(queryResultEdges: Edge[], isLinkPredictionData: boolean): UniqueEdge[] {
+  // Edges to be returned
+  const edges: UniqueEdge[] = [];
+
+  // Collect the edges in map, to only keep unique edges
+  // And count the number of same edges
+  const edgesMap = new Map<string, number>();
+  const attriMap = new Map<string, Record<string, any>>();
+  if (queryResultEdges != null) {
+    if (!isLinkPredictionData) {
+      for (let j = 0; j < queryResultEdges.length; j++) {
+        const newLink = queryResultEdges[j].from + ':' + queryResultEdges[j].to;
+        edgesMap.set(newLink, (edgesMap.get(newLink) || 0) + 1);
+        attriMap.set(newLink, queryResultEdges[j].attributes);
+      }
+
+      edgesMap.forEach((count, key) => {
+        const fromTo = key.split(':');
+        edges.push({
+          from: fromTo[0],
+          to: fromTo[1],
+          count: count,
+          attributes: attriMap.get(key) ?? [],
+        });
+      });
+    } else {
+      for (let i = 0; i < queryResultEdges.length; i++) {
+        edges.push({
+          from: queryResultEdges[i].from,
+          to: queryResultEdges[i].to,
+          count: queryResultEdges[i].attributes.jaccard_coefficient as number,
+          attributes: queryResultEdges[i].attributes,
+        });
+      }
+    }
+  }
+  return edges;
+}
+
+type OptionsI = {
+  defaultX?: number;
+  defaultY?: number;
+  defaultRadius?: number;
+};
+
+/**
+ * Parse a websocket message containing a query result into a node link GraphType.
+ * @param {any} queryResult An incoming query result from the websocket.
+ * @returns {GraphType} A node-link graph containing the nodes and links for the diagram.
+ */
+export function parseQueryResult(queryResult: GraphQueryResult, ml: ML, options: OptionsI = {}): GraphType {
+  const nodes: NodeType[] = [];
+  const typeDict: { [key: string]: number } = {};
+  // Counter for the types
+  let counter = 1;
+  // Entry to keep track of the number of machine learning clusters
+  let numberOfMlClusters = 0; // TODO
+
+  let communityDetectionInResult = false;
+  let shortestPathInResult = false;
+  let linkPredictionInResult = false;
+
+  for (let i = 0; i < queryResult.nodes.length; i++) {
+    // Assigns a group to every entity type for color coding
+    const nodeId = queryResult.nodes[i].id;
+    const entityType = queryResult.nodes[i].label;
+
+    // The preferred text to be shown on top of the node
+    let preferredText = nodeId;
+    let typeNumber = 1;
+
+    // Check if entity is already seen by the dictionary
+    if (entityType in typeDict) typeNumber = typeDict[entityType];
+    else {
+      typeDict[entityType] = counter;
+      typeNumber = counter;
+      counter++;
+    }
+
+    // TODO: this should be a setting
+    // Check to see if node has a "naam" attribute and set prefText to it
+    if (queryResult.nodes[i].attributes.name !== undefined) preferredText = queryResult.nodes[i].attributes.name as string;
+    if (queryResult.nodes[i].attributes.label !== undefined) preferredText = queryResult.nodes[i].attributes.label as string;
+    if (queryResult.nodes[i].attributes.naam !== undefined) preferredText = queryResult.nodes[i].attributes.naam as string;
+
+    let radius = options.defaultRadius || 5;
+    let data: NodeType = {
+      id: queryResult.nodes[i].id,
+      attributes: queryResult.nodes[i].attributes,
+      type: typeNumber,
+      displayInfo: preferredText,
+      radius: radius,
+      x: (options.defaultX || 0) + Math.random() * radius * 20 - radius * 10,
+      y: (options.defaultY || 0) + Math.random() * radius * 20 - radius * 10,
+    };
+
+    // let mlExtra = {};
+    // if (queryResult.nodes[i].mldata && typeof queryResult.nodes[i].mldata != 'number') { // TODO FIXME: this is somewhere else now
+    //   mlExtra = {
+    //     shortestPathData: queryResult.nodes[i].mldata as Record<string, string[]>,
+    //   };
+    //   shortestPathInResult = true;
+    // } else if (typeof queryResult.nodes[i].mldata == 'number') {
+    //   // mldata + 1 so you dont get 0, which is interpreted as 'undefined'
+    //   const numberOfCluster = (queryResult.nodes[i].mldata as number) + 1;
+    //   mlExtra = {
+    //     cluster: numberOfCluster,
+    //     clusterAccoringToMLData: numberOfCluster,
+    //   };
+    //   communityDetectionInResult = true;
+    //   if (numberOfCluster > numberOfMlClusters) {
+    //     numberOfMlClusters = numberOfCluster;
+    //   }
+    // }
+
+    // Add mlExtra to the node if necessary
+    // data = { ...data, ...mlExtra };
+    nodes.push(data);
+  }
+
+  // Filter unique edges and transform to LinkTypes
+  // List for all links
+  let links: LinkType[] = [];
+  let allNodeIds = new Set(nodes.map((n) => n.id));
+
+  // Parse ml edges
+  //   if (ml != undefined) {
+  //     ml?.linkPrediction?.forEach((link) => {
+  //       if (allNodeIds.has(link.from) && allNodeIds.has(link.to)) {
+  //         const toAdd: LinkType = {
+  //           source: link.from,
+  //           target: link.to,
+  //           value: link.attributes.jaccard_coefficient as number,
+  //           mlEdge: true,
+  //           color: 0x000000,
+  //         };
+  //         links.push(toAdd);
+  //       }
+  //       linkPredictionInResult = true;
+  //     });
+  //   }
+
+  // Parse normal edges
+  const uniqueEdges = parseToUniqueEdges(queryResult.edges, false);
+  for (let i = 0; i < uniqueEdges.length; i++) {
+    if (allNodeIds.has(uniqueEdges[i].from) && allNodeIds.has(uniqueEdges[i].to)) {
+      const toAdd: LinkType = {
+        id: uniqueEdges[i].from + ':' + uniqueEdges[i].to,
+        source: uniqueEdges[i].from,
+        target: uniqueEdges[i].to,
+        value: uniqueEdges[i].count,
+        mlEdge: false,
+        color: 0x000000,
+      };
+      links.push(toAdd);
+    }
+  }
+
+  //TODO: is this in use?
+  const maxCount = links.reduce(
+    (previousValue, currentValue) => (currentValue.value > previousValue ? currentValue.value : previousValue),
+    -1
+  );
+  //TODO: is this in use?
+  // Scale the value from 0 to 50
+  const maxLineWidth = 50;
+  if (maxCount > maxLineWidth) {
+    links.forEach((link) => {
+      link.value = (link.value / maxCount) * maxLineWidth;
+      link.value = link.value < 1 ? 1 : link.value;
+    });
+  }
+
+  // Graph to be returned
+  let toBeReturned: GraphType = {
+    nodes: nodes,
+    links: links,
+    // linkPrediction: linkPredictionInResult,
+    // shortestPath: shortestPathInResult,
+    // communityDetection: communityDetectionInResult,
+  };
+
+  // If query with community detection; add number of clusters to the graph
+  // const numberOfClusters = {
+  //   numberOfMlClusters: numberOfMlClusters,
+  // };
+  // if (communityDetectionInResult) {
+  //   toBeReturned = { ...toBeReturned, ...numberOfClusters };
+  // }
+
+
+  // return toBeReturned;
+  return processML(ml, toBeReturned);
+}
diff --git a/libs/shared/lib/vis/nodelink/components/utils.tsx b/libs/shared/lib/vis/nodelink/components/utils.tsx
index 84513035db3164c9c223119b23ee625e534ed120..20e348a3f753db21fbc0ff85ca7b1d848b6b5768 100644
--- a/libs/shared/lib/vis/nodelink/components/utils.tsx
+++ b/libs/shared/lib/vis/nodelink/components/utils.tsx
@@ -9,7 +9,7 @@ import { GraphType, LinkType, NodeType } from '../Types';
 export function nodeColor(num: number) {
   // num = num % 4;
   // const col = '#000000';
-  const col = tailwindColors.custom.nodes[num % tailwindColors.custom.nodes.length];
+  const col = tailwindColors.custom.nodes[num % (tailwindColors.custom.nodes.length - 1)];
   return binaryColor(col);
 }
 
diff --git a/libs/shared/lib/vis/nodelink/nodelinkvis.tsx b/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
index 58d75ce677e2b245f8ede222f08590add3cdd36e..47adee5747c89d16b7e5676794cc227de84db0c7 100644
--- a/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
+++ b/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
@@ -1,10 +1,14 @@
-import { GraphQueryResult, useAppDispatch, useGraphQueryResult } from '../../data-access/store';
+import { GraphQueryResult, useAppDispatch, useGraphQueryResult, useML } from '../../data-access/store';
 import React, { LegacyRef, useEffect, useRef, useState } from 'react';
 import styled from 'styled-components';
 import * as PIXI from 'pixi.js';
 import { GraphType, LinkType, NodeType } from './Types';
-import { useNLPixi } from './components/NLPixi';
+import { NLPixi } from './components/NLPixi';
 import { getRelatedLinks } from './components/utils';
+import { parseQueryResult } from './components/query2NL';
+import { processML } from './components/NLMachineLearning';
+import { useImmer } from 'use-immer';
+import { ML, setShortestPathSource, setShortestPathTarget } from '../../data-access/store/mlSlice';
 
 interface Props {
   loading?: boolean;
@@ -37,69 +41,83 @@ export interface NodeLinkComponentState {
 }
 
 export const NodeLinkVis = React.memo((props: Props) => {
-  const jaccardThreshold = undefined;
-  const myRef = useRef<HTMLDivElement>(null);
-  const [graph, setGraph] = useState<GraphType>();
-  const [windowSize, setWindowSize] = useState({ width: 1000, height: 1000 });
+  const ref = useRef<HTMLDivElement>(null);
+  const [graph, setGraph] = useImmer<GraphType | undefined>(undefined);
   const [highlightNodes, setHighlightNodes] = useState<NodeType[]>([]);
   const [highlightedLinks, setHighlightedLinks] = useState<LinkType[]>([]);
 
-  const pixi = useNLPixi({
-    windowSize: windowSize,
-    onClick: (node) => {
-      if (graph) {
-        //If node is already clicked we should remove it from the list.
-        if (highlightNodes.includes(node)) {
-          const index = highlightNodes.indexOf(node, 0);
-          setHighlightNodes(highlightNodes.splice(index, 1));
-        } else {
-          //Else add it to the list.
-          setHighlightNodes(highlightNodes.concat(node));
-        }
-        //Update the list of edges that need to be highlighted
-        setHighlightedLinks(getRelatedLinks(graph, highlightNodes, jaccardThreshold || -1));
-      }
-    },
-    highlightNodes: highlightNodes,
-    currentShortestPathEdges: [],
-    highlightedLinks: highlightedLinks,
-    jaccardThreshold: jaccardThreshold,
-    myDiv: myRef.current,
-  });
-
   const graphQueryResult = useGraphQueryResult();
+  const ml = useML();
+  const dispatch = useAppDispatch();
 
   useEffect(() => {
-    console.debug('update nodelink useEffect', graphQueryResult);
-    pixi.refresh(graphQueryResult);
-  }, [graphQueryResult]);
-
-  useEffect(() => {
-    console.log('loaded NodeLinkVis');
-    window.addEventListener('resize', handleResize);
-    // nodeLinkViewModel.subscribeToQueryResult();
-    // nodeLinkViewModel.setThisVisAsExportable();
-    // nodeLinkViewModel.subscribeToAnalyticsData();
-
-    return () => {
-      console.log('unloaded NodeLinkVis');
-      window.removeEventListener('resize', handleResize);
-    };
-  }, []);
-
-  useEffect(() => {
-    console.debug('loaded NodeLinkVis');
-    setWindowSize({ width: myRef?.current?.clientWidth || 1000, height: myRef?.current?.clientHeight || 1000 });
-  }, [myRef]);
-
-  const loading = props.loading;
+    if (graphQueryResult) {
+      setGraph(
+        parseQueryResult(graphQueryResult, ml, {
+          defaultX: (ref.current?.clientWidth || 1000) / 2,
+          defaultY: (ref.current?.clientHeight || 1000) / 2,
+        })
+      );
+    }
+  }, [graphQueryResult, ml]);
+
+  const onClickedNode = (node: NodeType, ml: ML) => {
+    console.log('shortestPath', graph, ml.shortestPath.enabled);
+    if (graph) {
+      // //If node is already clicked we should remove it from the list.
+      // if (highlightNodes.includes(node)) {
+      //   const index = highlightNodes.indexOf(node, 0);
+      //   setHighlightNodes(highlightNodes.splice(index, 1));
+      // } else {
+      //   //Else add it to the list.
+      //   setHighlightNodes(highlightNodes.concat(node));
+      // }
+      // //Update the list of edges that need to be highlighted
+      // setHighlightedLinks(getRelatedLinks(graph, highlightNodes, ml.communityDetection.jaccard_threshold || -1));
+
+      if (ml.shortestPath.enabled) {
+        console.log('shortestPath');
+
+        setGraph((draft) => {
+          let _node = draft?.nodes.find((n) => n.id === node.id);
+          if (!_node) return draft;
+
+          if (!ml.shortestPath.srcNode) {
+            _node.isShortestPathSource = true;
+            dispatch(setShortestPathSource(node.id));
+          } else if (ml.shortestPath.srcNode === node.id) {
+            _node.isShortestPathSource = false;
+            dispatch(setShortestPathSource(undefined));
+          } else if (!ml.shortestPath.trtNode) {
+            _node.isShortestPathTarget = true;
+            dispatch(setShortestPathTarget(node.id));
+          } else if (ml.shortestPath.trtNode === node.id) {
+            _node.isShortestPathTarget = false;
+            dispatch(setShortestPathTarget(undefined));
+          } else {
+            _node.isShortestPathSource = true;
+            _node.isShortestPathTarget = false;
+            dispatch(setShortestPathSource(node.id));
+            dispatch(setShortestPathTarget(undefined));
+          }
+          console.log('shortestPath', _node);
+          return draft;
+        });
+      }
+    }
+  };
 
-  // FUNCIONS FROM MODEL
+  // useEffect(() => {
+  //   if (ml && graph) {
+  //     // const g = processML(ml, graph as GraphType);
+  //     // console.log(g);
 
-  /** When the screen gets resized, resize the node link canvas ViewModel. */
-  const handleResize = () => {
-    setWindowSize({ width: window.innerWidth, height: window.innerHeight - 6 });
-  };
+  //     setGraph((draft) => {
+  //       const g = processML(ml, draft as GraphType);
+  //       return g;
+  //     });
+  //   }
+  // }, [ml]);
 
   return (
     <>
@@ -111,25 +129,28 @@ export const NodeLinkVis = React.memo((props: Props) => {
         name="head"
         value={theme.palette.primary.main}
       /> */}
-      {loading && (
-        <div className="w-full h-full flex justify-center self-center items-center">
-          <span className="loading loading-spinner loading-lg"></span>
-        </div>
-      )}
-      {!loading && (
-        <div className="h-full overflow-hidden">
-          <div className="h-full overflow-hidden" ref={myRef}></div>
-          {/* <VisConfigPanelComponent> */}
-          {/* <NodeLinkConfigPanelComponent
+      <div className="h-full w-full overflow-hidden" ref={ref}>
+        <NLPixi
+          graph={graph}
+          highlightNodes={highlightNodes}
+          highlightedLinks={highlightedLinks}
+          onClick={(node) => {
+            console.log(ml.shortestPath);
+
+            onClickedNode(node, ml);
+          }}
+        />
+
+        {/* <VisConfigPanelComponent> */}
+        {/* <NodeLinkConfigPanelComponent
                graph={this.state.graph}
                nlViewModel={this.nodeLinkViewModel}
             /> */}
-          {/*</VisConfigPanelComponent>*/}
-          {/*<VisConfigPanelComponent isLeft>*/}
-          {/*  <AttributesConfigPanel nodeLinkViewModel={this.nodeLinkViewModel} />*/}
-          {/* </VisConfigPanelComponent> */}
-        </div>
-      )}
+        {/*</VisConfigPanelComponent>*/}
+        {/*<VisConfigPanelComponent isLeft>*/}
+        {/*  <AttributesConfigPanel nodeLinkViewModel={this.nodeLinkViewModel} />*/}
+        {/* </VisConfigPanelComponent> */}
+      </div>
     </>
   );
 });
diff --git a/libs/shared/lib/vis/shared/ResultNodeLinkParserUseCase.tsx b/libs/shared/lib/vis/shared/ResultNodeLinkParserUseCase.tsx
index 3ba91aa1346155239d6b391c6156a34607d562ca..22fcd08e2fd28d29349a4066865409049e9fa8e7 100644
--- a/libs/shared/lib/vis/shared/ResultNodeLinkParserUseCase.tsx
+++ b/libs/shared/lib/vis/shared/ResultNodeLinkParserUseCase.tsx
@@ -5,6 +5,7 @@
  */
 import { GraphType, LinkType, NodeType } from '../nodelink/Types';
 import { Edge, Node, GraphQueryResult } from '../../data-access/store';
+import { ML } from '../../data-access/store/mlSlice';
 /** ResultNodeLinkParserUseCase implements methods to parse and translate websocket messages from the backend into a GraphType. */
 
 /**
@@ -129,143 +130,3 @@ export class ParseToUniqueEdges {
     return edges;
   }
 }
-
-export default class ResultNodeLinkParserUseCase {
-  /**
-   * Parse a websocket message containing a query result into a node link GraphType.
-   * @param {any} queryResult An incoming query result from the websocket.
-   * @returns {GraphType} A node-link graph containing the nodes and links for the diagram.
-   */
-  public parseQueryResult(queryResult: GraphQueryResult): GraphType {
-    const nodes: NodeType[] = [];
-    const typeDict: { [key: string]: number } = {};
-    // Counter for the types
-    let counter = 1;
-    // Entry to keep track of the number of machine learning clusters
-    let numberOfMlClusters = 0;
-
-    let communityDetectionInResult = false;
-    let shortestPathInResult = false;
-    let linkPredictionInResult = false;
-
-    for (let i = 0; i < queryResult.nodes.length; i++) {
-      // Assigns a group to every entity type for color coding
-      const nodeId = queryResult.nodes[i].id + '/';
-      const entityType = queryResult.nodes[i].label;
-
-      // The preferred text to be shown on top of the node
-      let preferredText = nodeId;
-      let typeNumber = 1;
-
-      // Check if entity is already seen by the dictionary
-      if (entityType in typeDict) typeNumber = typeDict[entityType];
-      else {
-        typeDict[entityType] = counter;
-        typeNumber = counter;
-        counter++;
-      }
-
-      // Check to see if node has a "naam" attribute and set prefText to it
-      if (queryResult.nodes[i].attributes.name != undefined) preferredText = queryResult.nodes[i].attributes.name as string;
-      if (queryResult.nodes[i].attributes.label != undefined) preferredText = queryResult.nodes[i].attributes.label as string;
-      if (queryResult.nodes[i].attributes.naam != undefined) preferredText = queryResult.nodes[i].attributes.naam as string;
-
-      let data: NodeType = {
-        id: queryResult.nodes[i].id,
-        attributes: queryResult.nodes[i].attributes,
-        type: typeNumber,
-        displayInfo: preferredText,
-        radius: 5,
-      };
-
-      let mlExtra = {};
-      if (queryResult.nodes[i].mldata && typeof queryResult.nodes[i].mldata != 'number') {
-        mlExtra = {
-          shortestPathData: queryResult.nodes[i].mldata as Record<string, string[]>,
-        };
-        shortestPathInResult = true;
-      } else if (typeof queryResult.nodes[i].mldata == 'number') {
-        // mldata + 1 so you dont get 0, which is interpreted as 'undefined'
-        const numberOfCluster = (queryResult.nodes[i].mldata as number) + 1;
-        mlExtra = {
-          cluster: numberOfCluster,
-          clusterAccoringToMLData: numberOfCluster,
-        };
-        communityDetectionInResult = true;
-        if (numberOfCluster > numberOfMlClusters) {
-          numberOfMlClusters = numberOfCluster;
-        }
-      }
-
-      // Add mlExtra to the node if necessary
-      data = { ...data, ...mlExtra };
-      nodes.push(data);
-    }
-
-    // Filter unique edges and transform to LinkTypes
-    // List for all links
-    let links: LinkType[] = [];
-    let allNodeIds = new Set(nodes.map((n) => n.id));
-    // Parse ml edges
-    if (queryResult.mlEdges != undefined) {
-      const uniqueMLEdges = ParseToUniqueEdges.parse(queryResult.mlEdges, true);
-      links = uniqueMLEdges.map((edge) => {
-        return {
-          source: edge.from,
-          target: edge.to,
-          value: edge.count,
-          mlEdge: true,
-        };
-      });
-      linkPredictionInResult = true;
-    }
-
-    // Parse normal edges
-    const uniqueEdges = ParseToUniqueEdges.parse(queryResult.edges, false);
-    for (let i = 0; i < uniqueEdges.length; i++) {
-      if (allNodeIds.has(uniqueEdges[i].from) && allNodeIds.has(uniqueEdges[i].to)) {
-        const toAdd: LinkType = {
-          source: uniqueEdges[i].from,
-          target: uniqueEdges[i].to,
-          value: uniqueEdges[i].count,
-          mlEdge: false,
-        };
-        links.push(toAdd);
-      }
-    }
-
-    //TODO: is this in use?
-    const maxCount = links.reduce(
-      (previousValue, currentValue) => (currentValue.value > previousValue ? currentValue.value : previousValue),
-      -1
-    );
-    //TODO: is this in use?
-    // Scale the value from 0 to 50
-    const maxLineWidth = 50;
-    if (maxCount > maxLineWidth) {
-      links.forEach((link) => {
-        link.value = (link.value / maxCount) * maxLineWidth;
-        link.value = link.value < 1 ? 1 : link.value;
-      });
-    }
-
-    // Graph to be returned
-    let toBeReturned = {
-      nodes: nodes,
-      links: links,
-      linkPrediction: linkPredictionInResult,
-      shortestPath: shortestPathInResult,
-      communityDetection: communityDetectionInResult,
-    };
-
-    // If query with community detection; add number of clusters to the graph
-    const numberOfClusters = {
-      numberOfMlClusters: numberOfMlClusters,
-    };
-    if (communityDetectionInResult) {
-      toBeReturned = { ...toBeReturned, ...numberOfClusters };
-    }
-
-    return toBeReturned;
-  }
-}