From fe767700ac067bc06d7b47e5a30c17a3dc133e64 Mon Sep 17 00:00:00 2001
From: Sivan Duijn <sivanduijn@gmail.com>
Date: Sat, 5 Mar 2022 16:41:22 +0100
Subject: [PATCH] feat(querybuilder): added custom edge lines and auto drag
 attributes along

---
 .../customFlowLines/connection.tsx            | 73 +++++++++++++++++++
 .../customFlowLines/connectionDrag.tsx        | 41 +++++++++++
 .../attributepill/attributepill.module.scss   | 12 ++-
 .../attributepill/attributepill.tsx           | 13 +++-
 .../customFlowPills/entitypill/entitypill.tsx | 24 +++---
 .../relationpill/relationpill.tsx             | 16 ++--
 .../querybuilder/querybuilder.module.scss     |  8 ++
 .../querybuilder/querybuilder.stories.tsx     | 28 ++++---
 .../components/querybuilder/querybuilder.tsx  | 14 +++-
 libs/querybuilder/usecases/src/index.ts       |  1 +
 .../usecases/src/lib/attribute/checkInput.ts  |  2 +
 .../usecases/src/lib/connPointsOnPills.ts     | 14 ++++
 .../src/lib/createReactFlowElements.ts        | 30 +++++++-
 .../store/src/lib/colorPaletteConfigSlice.ts  |  3 +
 .../src/lib/mapColorsConfigToMuiTheme.ts      | 24 ++++++
 package.json                                  |  2 +
 yarn.lock                                     | 39 +++++++++-
 17 files changed, 300 insertions(+), 44 deletions(-)
 create mode 100644 apps/web-graphpolaris/src/components/querybuilder/customFlowLines/connection.tsx
 create mode 100644 apps/web-graphpolaris/src/components/querybuilder/customFlowLines/connectionDrag.tsx
 create mode 100644 apps/web-graphpolaris/src/components/querybuilder/querybuilder.module.scss
 create mode 100644 libs/querybuilder/usecases/src/lib/connPointsOnPills.ts

diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowLines/connection.tsx b/apps/web-graphpolaris/src/components/querybuilder/customFlowLines/connection.tsx
new file mode 100644
index 000000000..618f67b6b
--- /dev/null
+++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowLines/connection.tsx
@@ -0,0 +1,73 @@
+import { handles } from '@graphpolaris/querybuilder/usecases';
+import React from 'react';
+import { EdgeProps, getSmoothStepPath, Position } from 'react-flow-renderer';
+
+/**
+ * A custom query element edge line component.
+ * @param {EdgeProps} param0 The coordinates for the start and end point, the id and the style.
+ */
+export default function ConnectionLine({
+  id,
+  sourceX,
+  sourceY,
+  targetX,
+  targetY,
+  style,
+  sourceHandleId,
+  targetHandleId,
+}: EdgeProps) {
+  //Centering the line
+  sourceY -= 3;
+  targetY -= 3;
+
+  // Correct line positions with hardcoded numbers, because react flow lacks this functionality
+  // if (sourceHandleId == ) sourceX += 2;
+
+  // if (targetHandleId == Handles.ToAttributeHandle) targetX += 2;
+
+  let spos: Position = Position.Bottom;
+  if (sourceHandleId == handles.relation.fromEntity) {
+    spos = Position.Left;
+    sourceX += 7;
+    sourceY += 3;
+  } else if (sourceHandleId == handles.relation.toEntity) {
+    spos = Position.Right;
+    sourceX -= 2;
+    sourceY -= 3;
+  } else if (
+    sourceHandleId !== undefined &&
+    sourceHandleId !== null &&
+    sourceHandleId.includes('functionHandle')
+  ) {
+    spos = Position.Top;
+    sourceX -= 4;
+    sourceY += 3;
+  }
+
+  let tpos: Position = Position.Bottom;
+  if (targetHandleId == handles.relation.fromEntity) {
+    tpos = Position.Left;
+    targetX += 7;
+    targetY += 3;
+  } else if (targetHandleId == handles.relation.toEntity) {
+    tpos = Position.Right;
+    targetX -= 2;
+    targetY -= 3;
+  }
+
+  // Create smoothstep line
+  const path = getSmoothStepPath({
+    sourceX: sourceX,
+    sourceY: sourceY,
+    sourcePosition: spos,
+    targetX: targetX,
+    targetY: targetY,
+    targetPosition: tpos,
+  });
+
+  return (
+    <g stroke="#2e2e2e">
+      <path id={id} fill="none" strokeWidth={3} style={style} d={path} />
+    </g>
+  );
+}
diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowLines/connectionDrag.tsx b/apps/web-graphpolaris/src/components/querybuilder/customFlowLines/connectionDrag.tsx
new file mode 100644
index 000000000..9932aecdd
--- /dev/null
+++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowLines/connectionDrag.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { ConnectionLineComponentProps } from 'react-flow-renderer';
+
+/**
+ * A custom query element to render the line when connecting flow elements.
+ * @param {ConnectionLineComponentProps} param0 Source and target coordinates of the edges.
+ */
+export default function ConnectionDragLine({
+  sourceX,
+  sourceY,
+  targetX,
+  targetY,
+}: ConnectionLineComponentProps) {
+  return (
+    <g>
+      <path
+        fill="none"
+        stroke="#222"
+        strokeWidth={2.5}
+        className="animated"
+        d={`M${sourceX},${sourceY}L ${targetX},${targetY}`}
+      />
+      <circle
+        cx={sourceX}
+        cy={sourceY}
+        fill="#fff"
+        r={3}
+        stroke="#222"
+        strokeWidth={1.5}
+      />
+      <circle
+        cx={targetX}
+        cy={targetY}
+        fill="#fff"
+        r={3}
+        stroke="#222"
+        strokeWidth={1.5}
+      />
+    </g>
+  );
+}
diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.module.scss b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.module.scss
index 5d3bd74fb..9ba5ba22c 100644
--- a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.module.scss
+++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.module.scss
@@ -35,22 +35,26 @@
 
 .attributeInput {
   float: right;
-  padding: 0 2ch 0 0;
+  padding: 0 1ch 0 0;
   display: flex;
   align-items: center;
 
   input {
-    background-color: lightgray;
+    background-color: rgba(100, 100, 100, 0.1);
     font-family: monospace;
     font-size: variables.$fontsize;
-    border: 1px solid darkgrey;
+    border: 1px solid rgba(100, 100, 100, 0.3);
     border-radius: 2px;
     height: variables.$height;
     outline: none;
     transition: border 0.3s;
+    color: black;
+    &::placeholder {
+      color: black;
+    }
 
     &:focus {
-      border: 1px solid grey;
+      border: 1px solid rgba(0, 0, 0, 0.3);
     }
   }
 }
diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.tsx b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.tsx
index 2c98aacf4..a2a70d95b 100644
--- a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.tsx
+++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.tsx
@@ -4,8 +4,6 @@ import {
 } from '@graphpolaris/querybuilder/usecases';
 import { useTheme } from '@mui/material';
 import React, { useMemo, useState } from 'react';
-import { Handle, Position } from 'react-flow-renderer';
-import { Handles } from '../entitypill/entitypill';
 import styles from './attributepill.module.scss';
 import AttributeOperatorSelect from './operatorselect';
 
@@ -15,7 +13,6 @@ import AttributeOperatorSelect from './operatorselect';
  */
 export const AttributeRFPill = React.memo(({ data }: { data: any }) => {
   const theme = useTheme();
-  const [readonly, setReadonly] = useState(true);
   const [value, setValue] = useState(data?.value || '');
 
   const onChange = (e: any) => {
@@ -37,11 +34,19 @@ export const AttributeRFPill = React.memo(({ data }: { data: any }) => {
     [data?.datatype]
   );
 
+  // Determine the backgroundcolor based on if the attribute is connected to a entity or relation
+  let bgcolor;
+  if (data?.attributeOfA == 'entity')
+    bgcolor = theme.palette.queryBuilder.entity.lighterbg;
+  else if (data?.attributeOfA == 'relation')
+    bgcolor = theme.palette.queryBuilder.relation.lighterbg;
+  else bgcolor = theme.palette.queryBuilder.attribute.background;
+
   return (
     <div
       className={styles.attribute}
       style={{
-        background: theme.palette.queryBuilder.attribute.background,
+        background: bgcolor,
         color: theme.palette.queryBuilder.text,
       }}
     >
diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.tsx b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.tsx
index bf0b15f7d..2598bf458 100644
--- a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.tsx
+++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.tsx
@@ -1,3 +1,4 @@
+import { handles } from '@graphpolaris/querybuilder/usecases';
 import { useTheme } from '@mui/material';
 import React, { useEffect } from 'react';
 import { FlowElement, Handle, Position } from 'react-flow-renderer';
@@ -10,16 +11,16 @@ import styles from './entitypill.module.scss';
 //   },
 // };
 
-/** Links need handles to what they are connected to (and which side) */
-export enum Handles {
-  RelationLeft = 'leftEntityHandle', //target
-  RelationRight = 'rightEntityHandle', //target
-  ToAttributeHandle = 'attributesHandle', //target
-  ToRelation = 'relationsHandle', //source
-  Attribute = 'AttributeHandle', //source
-  ReceiveFunction = 'receiveFunctionHandle', //target
-  FunctionBase = 'functionHandle_', // + name from FunctionTypes args //source
-}
+// /** Links need handles to what they are connected to (and which side) */
+// export enum Handles {
+//   RelationLeft = 'leftEntityHandle', //target
+//   RelationRight = 'rightEntityHandle', //target
+//   ToAttributeHandle = 'attributesHandle', //target
+//   ToRelation = 'relationsHandle', //source
+//   Attribute = 'AttributeHandle', //source
+//   ReceiveFunction = 'receiveFunctionHandle', //target
+//   FunctionBase = 'functionHandle_', // + name from FunctionTypes args //source
+// }
 
 /**
  * Component to render an entity flow element
@@ -37,10 +38,11 @@ export const EntityRFPill = React.memo(({ data }: { data: any }) => {
       }}
     >
       <Handle
-        id={Handles.ToRelation}
+        id={handles.entity.relation}
         type="source"
         position={Position.Bottom}
         className={styles.handleLeft}
+        style={data?.isConnected ? { backgroundColor: '#2e2e2e' } : {}}
       />
       {/* <Handle
         id={Handles.ToAttributeHandle}
diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.tsx b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.tsx
index 64612837e..34c0d57eb 100644
--- a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.tsx
+++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.tsx
@@ -1,6 +1,6 @@
+import { handles } from '@graphpolaris/querybuilder/usecases';
 import { useTheme } from '@mui/material';
 import { Handle, Position } from 'react-flow-renderer';
-import { Handles } from '../entitypill/entitypill';
 
 import styles from './relationpill.module.scss';
 
@@ -61,19 +61,25 @@ export default function RelationRFPill({ data }: { data: any }) {
         }}
       >
         <Handle
-          id={Handles.RelationLeft}
-          type="source"
+          id={handles.relation.fromEntity}
+          type="target"
           position={Position.Left}
           className={styles.handleLeft}
+          style={
+            data?.isFromEntityConnected ? { borderRightColor: '#2e2e2e' } : {} // TODO: this should be color from theme
+          }
         />
         <span className={styles.content} title={data.name}>
           {data.name}
         </span>
         <Handle
-          id={Handles.RelationRight}
-          type="target"
+          id={handles.relation.toEntity}
+          type="source"
           position={Position.Right}
           className={styles.handleRight}
+          style={
+            data?.isToEntityConnected ? { borderLeftColor: '#2e2e2e' } : {}
+          }
         />
       </div>
       <div
diff --git a/apps/web-graphpolaris/src/components/querybuilder/querybuilder.module.scss b/apps/web-graphpolaris/src/components/querybuilder/querybuilder.module.scss
new file mode 100644
index 000000000..db83f4696
--- /dev/null
+++ b/apps/web-graphpolaris/src/components/querybuilder/querybuilder.module.scss
@@ -0,0 +1,8 @@
+.reactflow {
+  width: 100%;
+  height: 500px;
+
+  // :global(.react-flow__edges) {
+  //   z-index: 4 !important;
+  // }
+}
diff --git a/apps/web-graphpolaris/src/components/querybuilder/querybuilder.stories.tsx b/apps/web-graphpolaris/src/components/querybuilder/querybuilder.stories.tsx
index a3de1a494..9727183be 100644
--- a/apps/web-graphpolaris/src/components/querybuilder/querybuilder.stories.tsx
+++ b/apps/web-graphpolaris/src/components/querybuilder/querybuilder.stories.tsx
@@ -5,15 +5,12 @@ import {
   setQuerybuilderNodes,
 } from '@graphpolaris/shared/data-access/store';
 import { GraphPolarisThemeProvider } from '@graphpolaris/shared/data-access/theme';
-import { AnyAction, configureStore, Store } from '@reduxjs/toolkit';
-import {
-  ComponentMeta,
-  ComponentStory,
-  ReactFramework,
-} from '@storybook/react';
+import { configureStore } from '@reduxjs/toolkit';
+import { ComponentMeta, ComponentStory } from '@storybook/react';
 import { Provider } from 'react-redux';
 import QueryBuilder from './querybuilder';
 import { MultiGraph } from 'graphology';
+import { handles } from '@graphpolaris/querybuilder/usecases';
 
 export default {
   component: QueryBuilder,
@@ -51,20 +48,27 @@ graph.addNode('3', {
 });
 graph.addNode('4', {
   type: 'attribute',
-  x: 170,
-  y: 180,
+  x: 130,
+  y: 120,
   name: 'Attr bool',
   datatype: 'bool',
   operator: 'EQ',
 });
 graph.addEdge('2', '1', { type: 'attribute_connection' });
 graph.addEdge('3', '1', { type: 'attribute_connection' });
-graph.addEdge('4', '1', { type: 'attribute_connection' });
-// graph.addEdge('0', '1', { type: 'entity_relation' });
+graph.addEdge('4', '0', { type: 'attribute_connection' });
+graph.addEdge('0', '1', {
+  type: 'entity_relation',
+  targetHandle: handles.relation.fromEntity,
+});
+// graph.addEdge('1', '0', {
+//   type: 'entity_relation',
+//   sourceHandle: handles.relation.entity,
+// });
 mockStore.dispatch(setQuerybuilderNodes(graph.export()));
 
-export const NoData = Template.bind({});
-NoData.decorators = [
+export const Simple = Template.bind({});
+Simple.decorators = [
   (story) => (
     <Provider store={mockStore}>
       <GraphPolarisThemeProvider>{story()}</GraphPolarisThemeProvider>
diff --git a/apps/web-graphpolaris/src/components/querybuilder/querybuilder.tsx b/apps/web-graphpolaris/src/components/querybuilder/querybuilder.tsx
index dd90aded3..8e42ebf76 100644
--- a/apps/web-graphpolaris/src/components/querybuilder/querybuilder.tsx
+++ b/apps/web-graphpolaris/src/components/querybuilder/querybuilder.tsx
@@ -16,6 +16,9 @@ import ReactFlow, {
   Background,
   Node,
 } from 'react-flow-renderer';
+import styles from './querybuilder.module.scss';
+import ConnectionLine from './customFlowLines/connection';
+import ConnectionDragLine from './customFlowLines/connectionDrag';
 import AttributeRFPill from './customFlowPills/attributepill/attributepill';
 import EntityRFPill from './customFlowPills/entitypill/entitypill';
 import RelationRFPill from './customFlowPills/relationpill/relationpill';
@@ -25,6 +28,9 @@ const nodeTypes = {
   relation: RelationRFPill,
   attribute: AttributeRFPill,
 };
+const edgeTypes = {
+  connection: ConnectionLine,
+};
 
 const onLoad = (reactFlowInstance: any) => {
   setTimeout(() => reactFlowInstance.fitView(), 0);
@@ -36,8 +42,6 @@ const QueryBuilder = (props: {}) => {
 
   const elements = useMemo(() => createReactFlowElements(nodes), [nodes]);
 
-  const graphStyles = { width: '100%', height: '500px' };
-
   const onNodeDrag = (
     event: React.MouseEvent<Element, MouseEvent>,
     node: Node<any>
@@ -64,12 +68,14 @@ const QueryBuilder = (props: {}) => {
       <ReactFlowProvider>
         <ReactFlow
           elements={elements}
-          style={graphStyles}
           snapGrid={[10, 10]}
-          // snapToGrid={true}
+          // snapToGrid
           nodeTypes={nodeTypes}
+          edgeTypes={edgeTypes}
+          connectionLineComponent={ConnectionDragLine}
           onLoad={onLoad}
           onNodeDrag={onNodeDrag}
+          className={styles.reactflow}
         >
           <Background gap={10} size={0.7} />
         </ReactFlow>
diff --git a/libs/querybuilder/usecases/src/index.ts b/libs/querybuilder/usecases/src/index.ts
index b0e1230da..587b11ec5 100644
--- a/libs/querybuilder/usecases/src/index.ts
+++ b/libs/querybuilder/usecases/src/index.ts
@@ -1,3 +1,4 @@
 export * from './lib/attribute/getAttributeBoolOperators';
 export * from './lib/attribute/checkInput';
 export * from './lib/createReactFlowElements';
+export * from './lib/connPointsOnPills';
diff --git a/libs/querybuilder/usecases/src/lib/attribute/checkInput.ts b/libs/querybuilder/usecases/src/lib/attribute/checkInput.ts
index 8c8b0e625..f48dd787c 100644
--- a/libs/querybuilder/usecases/src/lib/attribute/checkInput.ts
+++ b/libs/querybuilder/usecases/src/lib/attribute/checkInput.ts
@@ -22,6 +22,8 @@ export function CheckDatatypeConstraint(type: string, str: string): string {
       isBoolean(str) ? (res = toBoolean(str)) : (res = '');
       break;
     case 'int':
+    case 'float':
+    case 'number':
       isNumber(str) ? (res = '' + parseFloat(str)) : (res = '');
       break;
     default:
diff --git a/libs/querybuilder/usecases/src/lib/connPointsOnPills.ts b/libs/querybuilder/usecases/src/lib/connPointsOnPills.ts
new file mode 100644
index 000000000..355bdd8e5
--- /dev/null
+++ b/libs/querybuilder/usecases/src/lib/connPointsOnPills.ts
@@ -0,0 +1,14 @@
+// This file describes the connection points (handles) on a query builder pill
+// For example the connection from entity to left relation handle
+
+export const handles = {
+  entity: {
+    /** The handle for a connection from an entity to a relation pill */
+    relation: 'entity:to_relation',
+  },
+  relation: {
+    /** The handle for a connection from a relation to an entity pill */
+    toEntity: 'relation:to_entity',
+    fromEntity: 'relation:from_entity',
+  },
+};
diff --git a/libs/querybuilder/usecases/src/lib/createReactFlowElements.ts b/libs/querybuilder/usecases/src/lib/createReactFlowElements.ts
index 21e0dcea1..76b9e83c4 100644
--- a/libs/querybuilder/usecases/src/lib/createReactFlowElements.ts
+++ b/libs/querybuilder/usecases/src/lib/createReactFlowElements.ts
@@ -11,15 +11,40 @@ export function createReactFlowElements(graph: Graph): Elements<Node | Edge> {
 
     switch (attributes.type) {
       case 'entity':
+        data = {
+          name: attributes.name,
+          isConnected: graph
+            .neighbors(node)
+            .some((nb) => graph.getNodeAttribute(nb, 'type') == 'relation'),
+        };
+        break;
       case 'relation':
-        data = { name: attributes.name };
+        data = {
+          name: attributes.name,
+          isFromEntityConnected: graph
+            .inNeighbors(node)
+            .some((nb) => graph.getNodeAttribute(nb, 'type') == 'entity'),
+          isToEntityConnected: graph
+            .outNeighbors(node)
+            .some((nb) => graph.getNodeAttribute(nb, 'type') == 'entity'),
+        };
         break;
-      case 'attribute':
+      case 'attribute': {
+        const ERNeighbors = graph.outNeighbors(node).filter((nb) => {
+          const type = graph.getNodeAttribute(nb, 'type');
+          return type == 'entity' || type == 'relation';
+        });
+        let attributeOfA = '';
+        if (ERNeighbors.length > 0)
+          attributeOfA = graph.getNodeAttribute(ERNeighbors[0], 'type');
         data = {
           name: attributes.name,
           datatype: attributes.datatype,
           operator: attributes.operator,
+          attributeOfA: attributeOfA,
         };
+        break;
+      }
     }
 
     const RFNode: Node = {
@@ -39,6 +64,7 @@ export function createReactFlowElements(graph: Graph): Elements<Node | Edge> {
       id: edge,
       source: source,
       target: target,
+      type: 'connection',
       sourceHandle: attributes.sourceHandle,
       targetHandle: attributes.targetHandle,
     };
diff --git a/libs/shared/data-access/store/src/lib/colorPaletteConfigSlice.ts b/libs/shared/data-access/store/src/lib/colorPaletteConfigSlice.ts
index 521f63b8a..64d340262 100644
--- a/libs/shared/data-access/store/src/lib/colorPaletteConfigSlice.ts
+++ b/libs/shared/data-access/store/src/lib/colorPaletteConfigSlice.ts
@@ -10,12 +10,15 @@ export interface ExtraColorsForMui5 {
     text: string;
     entity: {
       background: string;
+      lighterbg?: string;
     };
     relation: {
       background: string;
+      lighterbg?: string;
     };
     attribute: {
       background: string;
+      lighterbg?: string;
     };
   };
 }
diff --git a/libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts b/libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts
index 975bb3ed0..aa9a6de51 100644
--- a/libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts
+++ b/libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts
@@ -1,5 +1,6 @@
 import { ColorPaletteConfig } from '@graphpolaris/shared/data-access/store';
 import { ThemeOptions } from '@mui/material/styles';
+import Color from 'color';
 
 /** Maps our color palette config (stored in redux) to the mui 5 theme format */
 export default function MapColorsConfigToMuiTheme(
@@ -20,11 +21,22 @@ export default function MapColorsConfigToMuiTheme(
                 background:
                   colorsConfig.darkPalette.custom.queryBuilder.entity
                     .background,
+                lighterbg: Color(
+                  colorsConfig.darkPalette.custom.queryBuilder.entity.background
+                )
+                  .lighten(0.2)
+                  .toString(),
               },
               relation: {
                 background:
                   colorsConfig.darkPalette.custom.queryBuilder.relation
                     .background,
+                lighterbg: Color(
+                  colorsConfig.darkPalette.custom.queryBuilder.relation
+                    .background
+                )
+                  .lighten(0.2)
+                  .toString(),
               },
               attribute: {
                 background:
@@ -43,11 +55,23 @@ export default function MapColorsConfigToMuiTheme(
                 background:
                   colorsConfig.lightPalette.custom.queryBuilder.entity
                     .background,
+                lighterbg: Color(
+                  colorsConfig.lightPalette.custom.queryBuilder.entity
+                    .background
+                )
+                  .lighten(0.2)
+                  .toString(),
               },
               relation: {
                 background:
                   colorsConfig.lightPalette.custom.queryBuilder.relation
                     .background,
+                lighterbg: Color(
+                  colorsConfig.lightPalette.custom.queryBuilder.relation
+                    .background
+                )
+                  .lighten(0.2)
+                  .toString(),
               },
               attribute: {
                 background:
diff --git a/package.json b/package.json
index c2b43c245..cf9c8e848 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
     "@types/cytoscape": "^3.19.4",
     "@types/react-grid-layout": "^1.3.0",
     "@types/styled-components": "^5.1.21",
+    "color": "^4.2.1",
     "core-js": "^3.6.5",
     "cytoscape": "^3.21.0",
     "graphology": "^0.24.0",
@@ -59,6 +60,7 @@
     "@svgr/webpack": "^5.4.0",
     "@testing-library/react": "12.1.2",
     "@testing-library/react-hooks": "7.0.2",
+    "@types/color": "^3.0.3",
     "@types/jest": "27.0.2",
     "@types/node": "16.11.7",
     "@types/react": "17.0.30",
diff --git a/yarn.lock b/yarn.lock
index 898701c93..2beea9136 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4072,7 +4072,7 @@
   dependencies:
     "@types/node" "*"
 
-"@types/color-convert@^2.0.0":
+"@types/color-convert@*", "@types/color-convert@^2.0.0":
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.0.tgz#8f5ee6b9e863dcbee5703f5a517ffb13d3ea4e22"
   integrity sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==
@@ -4084,6 +4084,13 @@
   resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
   integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
 
+"@types/color@^3.0.3":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.3.tgz#e6d8d72b7aaef4bb9fe80847c26c7c786191016d"
+  integrity sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==
+  dependencies:
+    "@types/color-convert" "*"
+
 "@types/connect-history-api-fallback@^1.3.5":
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae"
@@ -6581,16 +6588,32 @@ color-name@1.1.3:
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
   integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
 
-color-name@~1.1.4:
+color-name@^1.0.0, color-name@~1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
 
+color-string@^1.9.0:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.0.tgz#63b6ebd1bec11999d1df3a79a7569451ac2be8aa"
+  integrity sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==
+  dependencies:
+    color-name "^1.0.0"
+    simple-swizzle "^0.2.2"
+
 color-support@^1.1.2:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
   integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
 
+color@^4.2.1:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884"
+  integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw==
+  dependencies:
+    color-convert "^2.0.1"
+    color-string "^1.9.0"
+
 colord@^2.9.1:
   version "2.9.2"
   resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1"
@@ -10489,6 +10512,11 @@ is-arrayish@^0.2.1:
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
   integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
 
+is-arrayish@^0.3.1:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
+  integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
+
 is-bigint@^1.0.1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
@@ -15262,6 +15290,13 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3:
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af"
   integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==
 
+simple-swizzle@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
+  integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=
+  dependencies:
+    is-arrayish "^0.3.1"
+
 sisteransi@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
-- 
GitLab