From 9d863898ecac903d1d67049b457708fb54b0ed78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Tue, 12 Nov 2024 08:50:53 +0100
Subject: [PATCH 01/29] feature(vector-map): implement context menu

---
 .../clickHandleReaction.test.ts}              |  8 +-
 .../clickHandleReaction.ts}                   |  2 +-
 .../leftClickHandleAlias.test.ts              |  4 +-
 .../mouseLeftClick}/leftClickHandleAlias.ts   |  0
 .../mouseLeftClick}/onMapLeftClick.test.ts    | 16 ++--
 .../mouseLeftClick}/onMapLeftClick.ts         |  8 +-
 .../mouseRightClick/onMapRightClick.test.ts   | 94 +++++++++++++++++++
 .../mouseRightClick/onMapRightClick.ts        | 55 +++++++++++
 .../rightClickHandleAlias.test.ts             | 28 ++++++
 .../mouseRightClick/rightClickHandleAlias.ts  | 16 ++++
 .../listeners/useOlMapVectorListeners.test.ts |  4 +-
 .../listeners/useOlMapVectorListeners.ts      | 46 ++++++++-
 .../reactionsLayer/useOlMapReactionsLayer.ts  | 14 +--
 .../utils/listeners/useOlMapListeners.ts      |  4 +-
 src/models/fixtures/modelElementFixture.ts    |  9 ++
 src/models/modelElementSchema.ts              |  4 +-
 src/redux/bioEntity/bioEntity.reducers.ts     | 16 ++++
 src/redux/bioEntity/bioEntity.slice.ts        |  5 +-
 .../newReactions/newReactions.selectors.ts    |  5 +
 .../bioEntity/mapModelElementToBioEntity.ts   | 51 ++++++++++
 20 files changed, 353 insertions(+), 36 deletions(-)
 rename src/components/Map/MapViewer/MapViewerVector/listeners/{mapLeftClick/leftClickHandleReaction.test.ts => mouseClick/clickHandleReaction.test.ts} (89%)
 rename src/components/Map/MapViewer/MapViewerVector/listeners/{mapLeftClick/leftClickHandleReaction.ts => mouseClick/clickHandleReaction.ts} (98%)
 rename src/components/Map/MapViewer/MapViewerVector/listeners/{mapLeftClick => mouseClick/mouseLeftClick}/leftClickHandleAlias.test.ts (92%)
 rename src/components/Map/MapViewer/MapViewerVector/listeners/{mapLeftClick => mouseClick/mouseLeftClick}/leftClickHandleAlias.ts (100%)
 rename src/components/Map/MapViewer/MapViewerVector/listeners/{mapLeftClick => mouseClick/mouseLeftClick}/onMapLeftClick.test.ts (87%)
 rename src/components/Map/MapViewer/MapViewerVector/listeners/{mapLeftClick => mouseClick/mouseLeftClick}/onMapLeftClick.ts (87%)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias.ts
 create mode 100644 src/models/fixtures/modelElementFixture.ts
 create mode 100644 src/utils/bioEntity/mapModelElementToBioEntity.ts

diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.test.ts
similarity index 89%
rename from src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.test.ts
index 1bca3522..f6aab10a 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.test.ts
@@ -1,5 +1,4 @@
 /* eslint-disable no-magic-numbers */
-import { leftClickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction';
 import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
@@ -10,12 +9,13 @@ import { apiPath } from '@/redux/apiPath';
 import { HttpStatusCode } from 'axios';
 import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture';
 import { FEATURE_TYPE } from '@/constants/features';
+import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
 jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds');
 const eventBusDispatchEventSpy = jest.spyOn(PluginsEventBus, 'dispatchEvent');
 
-describe('leftClickHandleReaction', () => {
+describe('clickHandleReaction', () => {
   let dispatch: jest.Mock;
   let modelId = 1;
   let reactionId = 1;
@@ -54,7 +54,7 @@ describe('leftClickHandleReaction', () => {
         )
         .reply(HttpStatusCode.Ok, bioEntityFixture);
     });
-    await leftClickHandleReaction(dispatch, hasFitBounds)(feature, modelId);
+    await clickHandleReaction(dispatch, hasFitBounds)(feature, modelId);
     expect(dispatch).toHaveBeenCalledTimes(4);
     expect(dispatch).toHaveBeenCalledWith(openReactionDrawerById(reactionId));
     expect(dispatch).toHaveBeenCalledWith(selectTab(''));
@@ -68,7 +68,7 @@ describe('leftClickHandleReaction', () => {
       unwrap: jest.fn().mockResolvedValue(mockBioEntities),
     }));
 
-    await leftClickHandleReaction(dispatch, false)(feature, modelId);
+    await clickHandleReaction(dispatch, false)(feature, modelId);
 
     expect(searchFitBounds).not.toHaveBeenCalled();
   });
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.ts
similarity index 98%
rename from src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.ts
rename to src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.ts
index 1481cee4..676d0b49 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.ts
@@ -15,7 +15,7 @@ import { getBioEntitiesIdsFromReaction } from '@/components/Map/MapViewer/utils/
 import { FEATURE_TYPE } from '@/constants/features';
 
 /* prettier-ignore */
-export const leftClickHandleReaction =
+export const clickHandleReaction =
   (dispatch: AppDispatch, hasFitBounds = false) =>
     async (feature: FeatureLike, modelId: number): Promise<void> => {
       const id = feature.get('id');
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias.test.ts
similarity index 92%
rename from src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias.test.ts
index 82b8dfc2..46ca1e40 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias.test.ts
@@ -1,12 +1,12 @@
 /* eslint-disable no-magic-numbers */
-import { leftClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias';
+import { leftClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias';
 import { openBioEntityDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
 import { Feature } from 'ol';
 import { FEATURE_TYPE } from '@/constants/features';
 
-jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds');
+jest.mock('../../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds');
 const eventBusDispatchEventSpy = jest.spyOn(PluginsEventBus, 'dispatchEvent');
 
 describe('leftClickHandleAlias', () => {
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias.ts
similarity index 100%
rename from src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias.ts
rename to src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias.ts
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts
similarity index 87%
rename from src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.test.ts
rename to src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts
index e7fa9bc7..166b70df 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts
@@ -5,16 +5,16 @@ import { resetReactionsData } from '@/redux/reactions/reactions.slice';
 import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice';
 import { handleFeaturesClick } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick';
 import Map from 'ol/Map';
-import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick';
+import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick';
 import { Comment } from '@/types/models';
 import { Layer } from 'ol/layer';
 import SimpleGeometry from 'ol/geom/SimpleGeometry';
 import { Feature } from 'ol';
 import { FEATURE_TYPE } from '@/constants/features';
 import * as leftClickHandleAlias from './leftClickHandleAlias';
-import * as leftClickHandleReaction from './leftClickHandleReaction';
+import * as clickHandleReaction from '../clickHandleReaction';
 
-jest.mock('../../../utils/listeners/mapSingleClick/handleFeaturesClick', () => ({
+jest.mock('../../../../utils/listeners/mapSingleClick/handleFeaturesClick', () => ({
   handleFeaturesClick: jest.fn(),
 }));
 jest.mock('./leftClickHandleAlias', () => ({
@@ -22,11 +22,11 @@ jest.mock('./leftClickHandleAlias', () => ({
   ...jest.requireActual('./leftClickHandleAlias'),
 }));
 const leftClickHandleAliasSpy = jest.spyOn(leftClickHandleAlias, 'leftClickHandleAlias');
-jest.mock('./leftClickHandleReaction', () => ({
+jest.mock('../clickHandleReaction', () => ({
   __esModule: true,
-  ...jest.requireActual('./leftClickHandleReaction'),
+  ...jest.requireActual('../clickHandleReaction'),
 }));
-const leftClickHandleReactionSpy = jest.spyOn(leftClickHandleReaction, 'leftClickHandleReaction');
+const clickHandleReactionSpy = jest.spyOn(clickHandleReaction, 'clickHandleReaction');
 
 describe('onMapLeftClick', () => {
   const modelId = 1;
@@ -89,7 +89,7 @@ describe('onMapLeftClick', () => {
     expect(leftClickHandleAliasSpy).toHaveBeenCalledWith(dispatch);
   });
 
-  it('calls leftClickHandleReaction if feature type is REACTION', async () => {
+  it('calls clickHandleReaction if feature type is REACTION', async () => {
     const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }];
     const dispatch = jest.fn(() => ({
       unwrap: jest.fn().mockResolvedValue(mockBioEntities),
@@ -108,6 +108,6 @@ describe('onMapLeftClick', () => {
       comments,
     )(event, mapInstance);
 
-    expect(leftClickHandleReactionSpy).toHaveBeenCalledWith(dispatch);
+    expect(clickHandleReactionSpy).toHaveBeenCalledWith(dispatch);
   });
 });
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts
similarity index 87%
rename from src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.ts
rename to src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts
index aa481cd3..7b047dda 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts
@@ -8,12 +8,12 @@ import { latLngToPoint } from '@/utils/map/latLngToPoint';
 import { FeatureLike } from 'ol/Feature';
 import { closeDrawer } from '@/redux/drawer/drawer.slice';
 import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice';
-import { leftClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias';
+import { leftClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias';
 import { handleFeaturesClick } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick';
 import { resetReactionsData } from '@/redux/reactions/reactions.slice';
-import { leftClickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction';
 import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset';
 import { FEATURE_TYPE } from '@/constants/features';
+import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
 
 /* prettier-ignore */
 export const onMapLeftClick =
@@ -25,7 +25,7 @@ export const onMapLeftClick =
       dispatch(updateLastClick({ coordinates: point, modelId }));
 
       let featureAtPixel: FeatureLike | undefined;
-      mapInstance.forEachFeatureAtPixel(pixel, (feature) => {
+      mapInstance.forEachFeatureAtPixel(pixel, (feature, ) => {
         if(feature.get('id') && [...Object.values(FEATURE_TYPE)].includes(feature.get('type'))) {
           featureAtPixel = feature;
           return true;
@@ -55,6 +55,6 @@ export const onMapLeftClick =
       if(type === FEATURE_TYPE.ALIAS) {
         await leftClickHandleAlias(dispatch)(featureAtPixel, modelId);
       } else if (type === FEATURE_TYPE.REACTION) {
-        await leftClickHandleReaction(dispatch)(featureAtPixel, modelId);
+        await clickHandleReaction(dispatch)(featureAtPixel, modelId);
       }
     };
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts
new file mode 100644
index 00000000..362df7ae
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts
@@ -0,0 +1,94 @@
+/* eslint-disable no-magic-numbers */
+import { updateLastRightClick } from '@/redux/map/map.slice';
+import Map from 'ol/Map';
+import { onMapRightClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick';
+import { Layer } from 'ol/layer';
+import { openContextMenu } from '@/redux/contextMenu/contextMenu.slice';
+import { Source } from 'ol/source';
+import VectorLayer from 'ol/layer/Vector';
+import VectorSource from 'ol/source/Vector';
+import { Feature } from 'ol';
+import { FEATURE_TYPE } from '@/constants/features';
+import { modelElementsFixture } from '@/models/fixtures/modelElementsFixture';
+import * as rightClickHandleAlias from './rightClickHandleAlias';
+import * as clickHandleReaction from '../clickHandleReaction';
+
+jest.mock('./rightClickHandleAlias', () => ({
+  __esModule: true,
+  ...jest.requireActual('./rightClickHandleAlias'),
+}));
+const rightClickHandleAliasSpy = jest.spyOn(rightClickHandleAlias, 'rightClickHandleAlias');
+jest.mock('../clickHandleReaction', () => ({
+  __esModule: true,
+  ...jest.requireActual('../clickHandleReaction'),
+}));
+const clickHandleReactionSpy = jest.spyOn(clickHandleReaction, 'clickHandleReaction');
+
+describe('onMapRightClick', () => {
+  const modelId = 1;
+  let mapInstance: Map;
+  const event = { coordinate: [100, 50], pixel: [200, 100] };
+  const mapSize = {
+    width: 90,
+    height: 90,
+    tileSize: 256,
+    minZoom: 2,
+    maxZoom: 9,
+  };
+  let vectorLayer: VectorLayer;
+  let vectorSource: VectorSource;
+
+  beforeEach(() => {
+    const dummyElement = document.createElement('div');
+    mapInstance = new Map({ target: dummyElement });
+    vectorSource = new VectorSource({});
+    vectorLayer = new VectorLayer({
+      source: vectorSource,
+    });
+    vectorLayer.set('type', 'vectorMapLayer');
+    jest.clearAllMocks();
+  });
+
+  it('calls rightClickHandleAlias if feature type is ALIAS', async () => {
+    const dispatch = jest.fn();
+    const modelElement = modelElementsFixture.content[0];
+    jest.spyOn(mapInstance, 'getAllLayers').mockImplementation((): Layer<Source>[] => {
+      return [vectorLayer];
+    });
+    jest.spyOn(vectorLayer, 'isVisible').mockImplementation((): boolean => {
+      return true;
+    });
+    jest.spyOn(vectorLayer, 'getSource').mockImplementation((): VectorSource => {
+      return vectorSource;
+    });
+    jest.spyOn(vectorSource, 'getClosestFeatureToCoordinate').mockImplementation((): Feature => {
+      return new Feature({ id: modelElement.id, type: FEATURE_TYPE.ALIAS });
+    });
+    await onMapRightClick(mapSize, modelId, dispatch, [modelElement])(event, mapInstance);
+
+    expect(dispatch).toHaveBeenCalledWith(updateLastRightClick(expect.any(Object)));
+    expect(dispatch).toHaveBeenCalledWith(openContextMenu(event.pixel));
+    expect(rightClickHandleAliasSpy).toHaveBeenCalledWith(dispatch);
+  });
+
+  it('calls rightClickHandleAlias if feature type is REACTION', async () => {
+    const dispatch = jest.fn();
+    jest.spyOn(mapInstance, 'getAllLayers').mockImplementation((): Layer<Source>[] => {
+      return [vectorLayer];
+    });
+    jest.spyOn(vectorLayer, 'isVisible').mockImplementation((): boolean => {
+      return true;
+    });
+    jest.spyOn(vectorLayer, 'getSource').mockImplementation((): VectorSource => {
+      return vectorSource;
+    });
+    jest.spyOn(vectorSource, 'getClosestFeatureToCoordinate').mockImplementation((): Feature => {
+      return new Feature({ id: 1, type: FEATURE_TYPE.REACTION });
+    });
+    await onMapRightClick(mapSize, modelId, dispatch, [])(event, mapInstance);
+
+    expect(dispatch).toHaveBeenCalledWith(updateLastRightClick(expect.any(Object)));
+    expect(dispatch).toHaveBeenCalledWith(openContextMenu(event.pixel));
+    expect(clickHandleReactionSpy).toHaveBeenCalledWith(dispatch);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts
new file mode 100644
index 00000000..ae01e642
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts
@@ -0,0 +1,55 @@
+import { MapSize } from '@/redux/map/map.types';
+import { AppDispatch } from '@/redux/store';
+import { Feature, Map, MapBrowserEvent } from 'ol';
+import { updateLastRightClick } from '@/redux/map/map.slice';
+import { toLonLat } from 'ol/proj';
+import { latLngToPoint } from '@/utils/map/latLngToPoint';
+import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset';
+import { FEATURE_TYPE } from '@/constants/features';
+import VectorLayer from 'ol/layer/Vector';
+import VectorSource from 'ol/source/Vector';
+import { openContextMenu } from '@/redux/contextMenu/contextMenu.slice';
+import { ModelElement } from '@/types/models';
+import { rightClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias';
+import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
+
+/* prettier-ignore */
+export const onMapRightClick =
+  (mapSize: MapSize, modelId: number, dispatch: AppDispatch, modelElements: Array<ModelElement>) =>
+    async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => {
+
+      const [lng, lat] = toLonLat(coordinate);
+      const point = latLngToPoint([lat, lng], mapSize);
+      dispatch(updateLastRightClick({ coordinates: point, modelId }));
+
+      let foundFeature: Feature | undefined;
+      mapInstance.getAllLayers().forEach(layer => {
+        if(layer.isVisible() && layer instanceof VectorLayer) {
+          if (layer.get('type') === 'vectorMapLayer') {
+            const source = layer.getSource();
+            if (source instanceof VectorSource) {
+              foundFeature = source.getClosestFeatureToCoordinate(coordinate, (feature) => {
+                return [FEATURE_TYPE.ALIAS, FEATURE_TYPE.REACTION].includes(feature.get('type'));
+              });
+            }
+          }
+        }
+      });
+      if(!foundFeature) {
+        return;
+      }
+      dispatch(handleDataReset);
+      dispatch(openContextMenu(pixel));
+
+      const type = foundFeature.get('type');
+      const id = foundFeature.get('id');
+      if(type === FEATURE_TYPE.ALIAS) {
+        const modelElement = modelElements.find(element => element.id === id);
+        if(!modelElement) {
+          return;
+        }
+        await rightClickHandleAlias(dispatch)(id, modelElement);
+      } else if (type === FEATURE_TYPE.REACTION) {
+        await clickHandleReaction(dispatch)(foundFeature, modelId);
+      }
+    };
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias.test.ts
new file mode 100644
index 00000000..2b936519
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias.test.ts
@@ -0,0 +1,28 @@
+/* eslint-disable no-magic-numbers */
+import { rightClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias';
+import { modelElementFixture } from '@/models/fixtures/modelElementFixture';
+import { setBioEntityContents } from '@/redux/bioEntity/bioEntity.slice';
+import { addNumbersToEntityNumberData } from '@/redux/entityNumber/entityNumber.slice';
+import { setCurrentSelectedBioEntityId } from '@/redux/contextMenu/contextMenu.slice';
+
+jest.mock('../../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds');
+
+describe('rightClickHandleAlias', () => {
+  let dispatch: jest.Mock;
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+    dispatch = jest.fn();
+  });
+
+  it('dispatches setBioEntityContents, addNumbersToEntityNumberData and setCurrentSelectedBioEntityId', async () => {
+    await rightClickHandleAlias(dispatch)(modelElementFixture.id, modelElementFixture);
+    expect(dispatch).toHaveBeenCalledTimes(3);
+
+    expect(dispatch).toHaveBeenCalledWith(setBioEntityContents(expect.any(Object)));
+    expect(dispatch).toHaveBeenCalledWith(
+      addNumbersToEntityNumberData([modelElementFixture.elementId]),
+    );
+    expect(dispatch).toHaveBeenCalledWith(setCurrentSelectedBioEntityId(modelElementFixture.id));
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias.ts
new file mode 100644
index 00000000..4968eea6
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias.ts
@@ -0,0 +1,16 @@
+import { setCurrentSelectedBioEntityId } from '@/redux/contextMenu/contextMenu.slice';
+import { AppDispatch } from '@/redux/store';
+import { ModelElement } from '@/types/models';
+import { setBioEntityContents } from '@/redux/bioEntity/bioEntity.slice';
+import { addNumbersToEntityNumberData } from '@/redux/entityNumber/entityNumber.slice';
+import { mapModelElementToBioEntity } from '@/utils/bioEntity/mapModelElementToBioEntity';
+
+/* prettier-ignore */
+export const rightClickHandleAlias =
+  (dispatch: AppDispatch) =>
+    async (id: number, modelElement: ModelElement): Promise<void> => {
+      const bioEntity = mapModelElementToBioEntity(modelElement);
+      dispatch(setBioEntityContents({ bioEntity, perfect: true }));
+      dispatch(addNumbersToEntityNumberData([bioEntity.elementId]));
+      dispatch(setCurrentSelectedBioEntityId(id));
+    };
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.test.ts
index 64691511..8b772615 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.test.ts
@@ -4,9 +4,9 @@ import { View } from 'ol';
 import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
 import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
 import { useOlMapVectorListeners } from '@/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners';
-import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick';
+import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick';
 
-jest.mock('./mapLeftClick/onMapLeftClick', () => ({
+jest.mock('./mouseClick/mouseLeftClick/onMapLeftClick', () => ({
   __esModule: true,
   onMapLeftClick: jest.fn(),
 }));
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts
index ab9dd6a3..2a0af8ea 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts
@@ -5,22 +5,29 @@ import { mapDataSizeSelector } from '@/redux/map/map.selectors';
 import { currentModelIdSelector, vectorRenderingSelector } from '@/redux/models/models.selectors';
 import { MapInstance } from '@/types/map';
 import { unByKey } from 'ol/Observable';
-import { useEffect } from 'react';
+import { useEffect, useRef } from 'react';
 import { useSelector } from 'react-redux';
 import { useDebouncedCallback } from 'use-debounce';
 import { allCommentsSelectorOfCurrentMap } from '@/redux/comment/comment.selectors';
-import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick';
+import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { Coordinate } from 'ol/coordinate';
+import { Pixel } from 'ol/pixel';
+import { onMapRightClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick';
+import { modelElementsSelector } from '@/redux/modelElements/modelElements.selector';
 
-interface UseOlMapListenersInput {
+interface UseOlMapVectorListenersInput {
   mapInstance: MapInstance;
 }
 
-export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapListenersInput): void => {
+export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListenersInput): void => {
   const mapSize = useSelector(mapDataSizeSelector);
   const modelId = useSelector(currentModelIdSelector);
   const isResultDrawerOpen = useSelector(resultDrawerOpen);
+  const modelElements = useSelector(modelElementsSelector);
   const dispatch = useAppDispatch();
+  const coordinate = useRef<Coordinate>([]);
+  const pixel = useRef<Pixel>([]);
 
   const comments = useSelector(allCommentsSelectorOfCurrentMap);
   const vectorRendering = useAppSelector(vectorRenderingSelector);
@@ -31,6 +38,14 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapListenersInput)
     { leading: false },
   );
 
+  const handleRightClick = useDebouncedCallback(
+    onMapRightClick(mapSize, modelId, dispatch, modelElements?.content || []),
+    OPTIONS.clickPersistTime,
+    {
+      leading: false,
+    },
+  );
+
   useEffect(() => {
     if (!mapInstance || !vectorRendering) {
       return;
@@ -43,4 +58,27 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapListenersInput)
     // eslint-disable-next-line consistent-return
     return () => unByKey(key);
   }, [mapInstance, handleMapLeftClick, vectorRendering]);
+
+  useEffect(() => {
+    if (!mapInstance || !vectorRendering) {
+      return;
+    }
+
+    const rightClickEvent = (e: MouseEvent): Promise<void> | undefined => {
+      e.preventDefault();
+
+      coordinate.current = mapInstance.getEventCoordinate(e);
+      pixel.current = mapInstance.getEventPixel(e);
+
+      return handleRightClick(
+        { coordinate: coordinate.current, pixel: pixel.current },
+        mapInstance,
+      );
+    };
+
+    mapInstance.getViewport().addEventListener('contextmenu', rightClickEvent);
+
+    // eslint-disable-next-line consistent-return
+    return () => mapInstance.getViewport().removeEventListener('contextmenu', rightClickEvent);
+  }, [mapInstance, handleRightClick, vectorRendering]);
 };
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index e2d4fca4..5fbc2ae0 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -182,11 +182,11 @@ export const useOlMapReactionsLayer = ({
     });
   }, [features]);
 
-  return useMemo(
-    () =>
-      new VectorLayer({
-        source: vectorSource,
-      }),
-    [vectorSource],
-  );
+  return useMemo(() => {
+    const vectorLayer = new VectorLayer({
+      source: vectorSource,
+    });
+    vectorLayer.set('type', 'vectorMapLayer');
+    return vectorLayer;
+  }, [vectorSource]);
 };
diff --git a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
index d10e31d5..e7df6b03 100644
--- a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
+++ b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
@@ -126,7 +126,7 @@ export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput)
   }, [mapInstance, handleMapSingleClick, vectorRendering]);
 
   useEffect(() => {
-    if (!mapInstance) {
+    if (!mapInstance || vectorRendering) {
       return;
     }
 
@@ -143,5 +143,5 @@ export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput)
 
     // eslint-disable-next-line consistent-return
     return () => mapInstance.getViewport().removeEventListener('contextmenu', rightClickEvent);
-  }, [mapInstance, handleRightClick]);
+  }, [mapInstance, handleRightClick, vectorRendering]);
 };
diff --git a/src/models/fixtures/modelElementFixture.ts b/src/models/fixtures/modelElementFixture.ts
new file mode 100644
index 00000000..0936dceb
--- /dev/null
+++ b/src/models/fixtures/modelElementFixture.ts
@@ -0,0 +1,9 @@
+import { ZOD_SEED } from '@/constants';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { createFixture } from 'zod-fixture';
+import { modelElementSchema } from '@/models/modelElementSchema';
+
+export const modelElementFixture = createFixture(modelElementSchema, {
+  seed: ZOD_SEED,
+  array: { min: 3, max: 3 },
+});
diff --git a/src/models/modelElementSchema.ts b/src/models/modelElementSchema.ts
index 98f28825..b95b6f6a 100644
--- a/src/models/modelElementSchema.ts
+++ b/src/models/modelElementSchema.ts
@@ -7,9 +7,11 @@ import { glyphSchema } from '@/models/glyphSchema';
 
 export const modelElementSchema = z.object({
   id: z.number(),
-  model: z.number().nullable(),
+  model: z.number(),
   glyph: glyphSchema.nullable(),
   submodel: submodelSchema.nullable(),
+  compartment: z.number().nullable(),
+  elementId: z.string(),
   x: z.number(),
   y: z.number(),
   z: z.number(),
diff --git a/src/redux/bioEntity/bioEntity.reducers.ts b/src/redux/bioEntity/bioEntity.reducers.ts
index 3f68a154..deadfae3 100644
--- a/src/redux/bioEntity/bioEntity.reducers.ts
+++ b/src/redux/bioEntity/bioEntity.reducers.ts
@@ -1,6 +1,7 @@
 import { DEFAULT_ERROR } from '@/constants/errors';
 import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit';
 import { getBioEntityById } from '@/redux/bioEntity/thunks/getBioEntity';
+import { BioEntityContent } from '@/types/models';
 import { BIOENTITY_SUBMAP_CONNECTIONS_INITIAL_STATE } from './bioEntity.constants';
 import { getBioEntity, getMultiBioEntity } from './bioEntity.thunks';
 import { BioEntityContentsState } from './bioEntity.types';
@@ -120,3 +121,18 @@ export const toggleIsContentTabOpenedReducer = (
 ): void => {
   state.isContentTabOpened = action.payload;
 };
+
+export const setBioEntityContentsReducer = (
+  state: BioEntityContentsState,
+  action: PayloadAction<BioEntityContent>,
+): void => {
+  state.data = [
+    {
+      data: [action.payload],
+      loading: 'succeeded',
+      error: DEFAULT_ERROR,
+      searchQueryElement: action.payload.bioEntity.id.toString(),
+    },
+  ];
+  state.loading = 'succeeded';
+};
diff --git a/src/redux/bioEntity/bioEntity.slice.ts b/src/redux/bioEntity/bioEntity.slice.ts
index 728467a9..40f2bbb9 100644
--- a/src/redux/bioEntity/bioEntity.slice.ts
+++ b/src/redux/bioEntity/bioEntity.slice.ts
@@ -5,6 +5,7 @@ import {
   getBioEntityContentsReducer,
   getMultiBioEntityContentsReducer,
   getSubmapConnectionsBioEntityReducer,
+  setBioEntityContentsReducer,
   toggleIsContentTabOpenedReducer,
 } from './bioEntity.reducers';
 
@@ -14,6 +15,7 @@ export const bioEntityContentsSlice = createSlice({
   reducers: {
     clearBioEntitiesData: clearBioEntitiesDataReducer,
     toggleIsContentTabOpened: toggleIsContentTabOpenedReducer,
+    setBioEntityContents: setBioEntityContentsReducer,
   },
   extraReducers: builder => {
     getBioEntityContentsReducer(builder);
@@ -22,6 +24,7 @@ export const bioEntityContentsSlice = createSlice({
   },
 });
 
-export const { clearBioEntitiesData, toggleIsContentTabOpened } = bioEntityContentsSlice.actions;
+export const { clearBioEntitiesData, toggleIsContentTabOpened, setBioEntityContents } =
+  bioEntityContentsSlice.actions;
 
 export default bioEntityContentsSlice.reducer;
diff --git a/src/redux/newReactions/newReactions.selectors.ts b/src/redux/newReactions/newReactions.selectors.ts
index 4dc2babe..fd9978fd 100644
--- a/src/redux/newReactions/newReactions.selectors.ts
+++ b/src/redux/newReactions/newReactions.selectors.ts
@@ -7,3 +7,8 @@ export const newReactionsDataSelector = createSelector(
   newReactionsSelector,
   reactions => reactions.data || [],
 );
+
+// export const newReactionsReactionByIdSelector = createSelector(
+//   newReactionsDataSelector,
+//   reactions => reactions.find(reaction => reaction.id === id) || [],
+// );
diff --git a/src/utils/bioEntity/mapModelElementToBioEntity.ts b/src/utils/bioEntity/mapModelElementToBioEntity.ts
new file mode 100644
index 00000000..e8be81f2
--- /dev/null
+++ b/src/utils/bioEntity/mapModelElementToBioEntity.ts
@@ -0,0 +1,51 @@
+import { BioEntity, ModelElement } from '@/types/models';
+
+export function mapModelElementToBioEntity(modelElement: ModelElement): BioEntity {
+  return {
+    id: modelElement.id,
+    name: modelElement.name,
+    model: modelElement.model,
+    elementId: modelElement.elementId,
+    references: modelElement.references,
+    z: modelElement.z,
+    notes: modelElement.notes,
+    symbol: modelElement.symbol,
+    homodimer: modelElement.homodimer,
+    nameX: modelElement.nameX,
+    nameY: modelElement.nameY,
+    nameWidth: modelElement.nameWidth,
+    nameHeight: modelElement.nameHeight,
+    nameVerticalAlign: modelElement.nameVerticalAlign,
+    nameHorizontalAlign: modelElement.nameHorizontalAlign,
+    width: modelElement.width,
+    height: modelElement.height,
+    visibilityLevel: modelElement.visibilityLevel,
+    transparencyLevel: modelElement.transparencyLevel,
+    synonyms: modelElement.synonyms,
+    formerSymbols: modelElement.formerSymbols,
+    fullName: modelElement.fullName,
+    abbreviation: modelElement.abbreviation,
+    formula: modelElement.formula,
+    glyph: modelElement.glyph,
+    activity: modelElement.activity,
+    hypothetical: modelElement.hypothetical,
+    boundaryCondition: modelElement.boundaryCondition,
+    constant: modelElement.constant,
+    initialAmount: modelElement.initialAmount,
+    initialConcentration: modelElement.initialConcentration,
+    charge: modelElement.charge,
+    substanceUnits: modelElement.substanceUnits,
+    onlySubstanceUnits: modelElement.onlySubstanceUnits,
+    modificationResidues: modelElement.modificationResidues,
+    complex: modelElement.complex,
+    submodel: modelElement.submodel,
+    x: modelElement.x,
+    y: modelElement.y,
+    lineWidth: modelElement.lineWidth,
+    fontColor: modelElement.fontColor,
+    fontSize: modelElement.fontSize,
+    fillColor: modelElement.fillColor,
+    borderColor: modelElement.borderColor,
+    sboTerm: modelElement.sboTerm,
+  } as BioEntity;
+}
-- 
GitLab


From d7503d3348a2858848d1f236079fe8e56079ca5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Tue, 12 Nov 2024 12:14:03 +0100
Subject: [PATCH 02/29] feat(vector-map): handle user action using only
 frontend data

---
 .../mouseClick/clickHandleReaction.test.ts    |  15 +-
 .../mouseClick/clickHandleReaction.ts         |  90 ++++----
 .../getModelElementsIdsFromReaction.test.ts   |  24 +++
 .../getModelElementsIdsFromReaction.ts        |   7 +
 .../mouseLeftClick/onMapLeftClick.test.ts     |   6 +
 .../mouseLeftClick/onMapLeftClick.ts          |   7 +-
 .../mouseRightClick/onMapRightClick.test.ts   |  22 +-
 .../mouseRightClick/onMapRightClick.ts        |   6 +-
 .../listeners/useOlMapVectorListeners.ts      |  14 +-
 src/redux/bioEntity/bioEntity.reducers.ts     |  22 +-
 src/redux/bioEntity/bioEntity.slice.ts        |  10 +-
 .../entityNumber/entityNumber.reducers.ts     |   1 -
 .../newReactions/newReactions.selectors.ts    |   5 -
 src/redux/reactions/reactions.reducers.ts     |  11 +-
 src/redux/reactions/reactions.slice.ts        |   9 +-
 src/utils/bioEntity/mapReactionToBioEntity.ts |  19 ++
 src/utils/reaction/ReactionTypeEnum.ts        |  30 +++
 .../reaction/mapNewReactionToReaction.test.ts | 193 ++++++++++++++++++
 .../reaction/mapNewReactionToReaction.ts      |  45 ++++
 19 files changed, 454 insertions(+), 82 deletions(-)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.ts
 create mode 100644 src/utils/bioEntity/mapReactionToBioEntity.ts
 create mode 100644 src/utils/reaction/ReactionTypeEnum.ts
 create mode 100644 src/utils/reaction/mapNewReactionToReaction.test.ts
 create mode 100644 src/utils/reaction/mapNewReactionToReaction.ts

diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.test.ts
index f6aab10a..5282e246 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.test.ts
@@ -2,14 +2,13 @@
 import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
-import { Feature } from 'ol';
 import { reactionsFixture } from '@/models/fixtures/reactionFixture';
 import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
 import { apiPath } from '@/redux/apiPath';
 import { HttpStatusCode } from 'axios';
 import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture';
-import { FEATURE_TYPE } from '@/constants/features';
 import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
+import { newReactionFixture } from '@/models/fixtures/newReactionFixture';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
 jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds');
@@ -20,7 +19,6 @@ describe('clickHandleReaction', () => {
   let modelId = 1;
   let reactionId = 1;
   const hasFitBounds = true;
-  const feature = new Feature({ type: FEATURE_TYPE.REACTION, id: 1 });
 
   beforeEach(() => {
     jest.clearAllMocks();
@@ -54,8 +52,13 @@ describe('clickHandleReaction', () => {
         )
         .reply(HttpStatusCode.Ok, bioEntityFixture);
     });
-    await clickHandleReaction(dispatch, hasFitBounds)(feature, modelId);
-    expect(dispatch).toHaveBeenCalledTimes(4);
+    clickHandleReaction(dispatch, hasFitBounds)(
+      [],
+      [{ ...newReactionFixture, id: reactionId }],
+      reactionId,
+      modelId,
+    );
+    expect(dispatch).toHaveBeenCalledTimes(5);
     expect(dispatch).toHaveBeenCalledWith(openReactionDrawerById(reactionId));
     expect(dispatch).toHaveBeenCalledWith(selectTab(''));
     expect(eventBusDispatchEventSpy).toHaveBeenCalled();
@@ -68,7 +71,7 @@ describe('clickHandleReaction', () => {
       unwrap: jest.fn().mockResolvedValue(mockBioEntities),
     }));
 
-    await clickHandleReaction(dispatch, false)(feature, modelId);
+    clickHandleReaction(dispatch, false)([], [{ ...newReactionFixture, id: 1 }], 1, modelId);
 
     expect(searchFitBounds).not.toHaveBeenCalled();
   });
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.ts
index 676d0b49..392e4c3e 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.ts
@@ -1,68 +1,56 @@
-import { FIRST_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
 import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
-import { getReactionsByIds } from '@/redux/reactions/reactions.thunks';
 import { AppDispatch } from '@/redux/store';
 import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
-import { BioEntity } from '@/types/models';
-import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
-import { apiPath } from '@/redux/apiPath';
-import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
-import { bioEntitySchema } from '@/models/bioEntitySchema';
-import { getMultiBioEntityByIds } from '@/redux/bioEntity/thunks/getMultiBioEntity';
-import { FeatureLike } from 'ol/Feature';
-import { getBioEntitiesIdsFromReaction } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/getBioEntitiesIdsFromReaction';
+import { BioEntity, ModelElement, NewReaction } from '@/types/models';
 import { FEATURE_TYPE } from '@/constants/features';
+import { setMultipleBioEntityContents } from '@/redux/bioEntity/bioEntity.slice';
+import { addNumbersToEntityNumberData } from '@/redux/entityNumber/entityNumber.slice';
+import { setReactions } from '@/redux/reactions/reactions.slice';
+import mapNewReactionToReaction from '@/utils/reaction/mapNewReactionToReaction';
+import { mapReactionToBioEntity } from '@/utils/bioEntity/mapReactionToBioEntity';
+import getModelElementsIdsFromReaction from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction';
+import { mapModelElementToBioEntity } from '@/utils/bioEntity/mapModelElementToBioEntity';
 
 /* prettier-ignore */
 export const clickHandleReaction =
   (dispatch: AppDispatch, hasFitBounds = false) =>
-    async (feature: FeatureLike, modelId: number): Promise<void> => {
-      const id = feature.get('id');
-      const data = await dispatch(getReactionsByIds([id]));
-      const payload = data?.payload;
-      if (!data || !payload || typeof payload === 'string' || payload.data.length === SIZE_OF_EMPTY_ARRAY) {
+    ( modelElements: Array<ModelElement>, reactions: Array<NewReaction>, reactionId: number, modelId: number): void => {
+
+      const reactionBioEntities: Array<BioEntity> = [];
+      const reaction = reactions.find(newReaction => newReaction.id === reactionId);
+      if(!reaction) {
         return;
       }
+      const modelElementsIds = getModelElementsIdsFromReaction(reaction);
+      modelElementsIds.forEach(modelElementId => {
+        const modelElement = modelElements.find(element =>
+          element.id === modelElementId
+        );
+        if(!modelElement) {
+          return;
+        }
+        reactionBioEntities.push(mapModelElementToBioEntity(modelElement));
+      });
 
-      const reaction = payload.data[FIRST_ARRAY_ELEMENT];
-
-      const bioEntitiesIds = getBioEntitiesIdsFromReaction(reaction);
-
-      dispatch(openReactionDrawerById(reaction.id));
+      dispatch(openReactionDrawerById(reactionId));
 
       dispatch(selectTab(''));
 
-      const response = await axiosInstanceNewAPI.get<BioEntity>(apiPath.getReactionByIdInNewApi(reaction.id, reaction.modelId));
-      const isDataValid = validateDataUsingZodSchema(response.data, bioEntitySchema);
-
-      if (isDataValid) {
-        const reactionNewApi = response.data;
-
-        const bioEntities = await dispatch(
-          getMultiBioEntityByIds({
-            elementsToFetch: bioEntitiesIds.map((bioEntityId) => {
-              return {
-                elementId: parseInt(bioEntityId, 10),
-                modelId,
-                type: FEATURE_TYPE.ALIAS
-              };
-            })
-          })
-        ).unwrap();
-
-        if (bioEntities) {
-          const result = bioEntities.map((bioEntity) => {return { bioEntity, perfect: true };});
-          result.push({ bioEntity: reactionNewApi, perfect: true });
-          PluginsEventBus.dispatchEvent('onSearch', {
-            type: 'reaction',
-            searchValues: [{ id, modelId, type: FEATURE_TYPE.REACTION }],
-            results: [result]
-          });
-
-          if (hasFitBounds) {
-            searchFitBounds();
-          }
-        }
+      const bioEntityReaction = mapReactionToBioEntity(reaction);
+      dispatch(setMultipleBioEntityContents(reactionBioEntities));
+      dispatch(addNumbersToEntityNumberData(reactionBioEntities.map(reactionBioEntity => reactionBioEntity.elementId)));
+      dispatch(setReactions([mapNewReactionToReaction(reaction)]));
+
+      const result = reactionBioEntities.map((bioEntity) => {return { bioEntity, perfect: true };});
+      result.push({ bioEntity: bioEntityReaction, perfect: true });
+      PluginsEventBus.dispatchEvent('onSearch', {
+        type: 'reaction',
+        searchValues: [{ id: reactionId, modelId, type: FEATURE_TYPE.REACTION }],
+        results: [result]
+      });
+
+      if (hasFitBounds) {
+        searchFitBounds();
       }
     };
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.test.ts
new file mode 100644
index 00000000..fec3d634
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.test.ts
@@ -0,0 +1,24 @@
+/* eslint-disable no-magic-numbers */
+import { newReactionFixture } from '@/models/fixtures/newReactionFixture';
+import getModelElementsIdsFromReaction from './getModelElementsIdsFromReaction';
+
+describe('getModelElementsIdsFromReaction', () => {
+  it('should return correct model elements ids from given reaction', () => {
+    const result = getModelElementsIdsFromReaction(newReactionFixture);
+    expect(result).toEqual([
+      ...newReactionFixture.products.map(product => product.element),
+      ...newReactionFixture.reactants.map(reactant => reactant.element),
+      ...newReactionFixture.modifiers.map(modifier => modifier.element),
+    ]);
+  });
+
+  it('should return empty array', () => {
+    const result = getModelElementsIdsFromReaction({
+      ...newReactionFixture,
+      products: [],
+      reactants: [],
+      modifiers: [],
+    });
+    expect(result).toEqual([]);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.ts
new file mode 100644
index 00000000..a3ed0a9f
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.ts
@@ -0,0 +1,7 @@
+import { NewReaction } from '@/types/models';
+
+export default function getModelElementsIdsFromReaction(reaction: NewReaction): Array<number> {
+  return [...reaction.products, ...reaction.reactants, ...reaction.modifiers].map(
+    bioEntity => bioEntity.element,
+  );
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts
index 166b70df..373c01d1 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts
@@ -59,6 +59,8 @@ describe('onMapLeftClick', () => {
       dispatch,
       isResultDrawerOpen,
       comments,
+      [],
+      [],
     )(event, mapInstance);
 
     expect(dispatch).toHaveBeenCalledWith(updateLastClick(expect.any(Object)));
@@ -84,6 +86,8 @@ describe('onMapLeftClick', () => {
       dispatch,
       isResultDrawerOpen,
       comments,
+      [],
+      [],
     )(event, mapInstance);
 
     expect(leftClickHandleAliasSpy).toHaveBeenCalledWith(dispatch);
@@ -106,6 +110,8 @@ describe('onMapLeftClick', () => {
       dispatch,
       isResultDrawerOpen,
       comments,
+      [],
+      [],
     )(event, mapInstance);
 
     expect(clickHandleReactionSpy).toHaveBeenCalledWith(dispatch);
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts
index 7b047dda..7a2dd125 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts
@@ -1,7 +1,7 @@
 import { MapSize } from '@/redux/map/map.types';
 import { AppDispatch } from '@/redux/store';
 import { Map, MapBrowserEvent } from 'ol';
-import { Comment } from '@/types/models';
+import { Comment, ModelElement, NewReaction } from '@/types/models';
 import { updateLastClick } from '@/redux/map/map.slice';
 import { toLonLat } from 'ol/proj';
 import { latLngToPoint } from '@/utils/map/latLngToPoint';
@@ -17,7 +17,7 @@ import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/
 
 /* prettier-ignore */
 export const onMapLeftClick =
-  (mapSize: MapSize, modelId: number, dispatch: AppDispatch, isResultDrawerOpen: boolean, comments: Comment[]) =>
+  (mapSize: MapSize, modelId: number, dispatch: AppDispatch, isResultDrawerOpen: boolean, comments: Comment[], modelElements: Array<ModelElement>, reactions: Array<NewReaction>) =>
     async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => {
       const [lng, lat] = toLonLat(coordinate);
       const point = latLngToPoint([lat, lng], mapSize);
@@ -52,9 +52,10 @@ export const onMapLeftClick =
       dispatch(handleDataReset);
 
       const type = featureAtPixel.get('type');
+      const id = featureAtPixel.get('id');
       if(type === FEATURE_TYPE.ALIAS) {
         await leftClickHandleAlias(dispatch)(featureAtPixel, modelId);
       } else if (type === FEATURE_TYPE.REACTION) {
-        await clickHandleReaction(dispatch)(featureAtPixel, modelId);
+        clickHandleReaction(dispatch)(modelElements, reactions, id,  modelId);
       }
     };
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts
index 362df7ae..4502edd0 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts
@@ -9,7 +9,8 @@ import VectorLayer from 'ol/layer/Vector';
 import VectorSource from 'ol/source/Vector';
 import { Feature } from 'ol';
 import { FEATURE_TYPE } from '@/constants/features';
-import { modelElementsFixture } from '@/models/fixtures/modelElementsFixture';
+import { modelElementFixture } from '@/models/fixtures/modelElementFixture';
+import { newReactionFixture } from '@/models/fixtures/newReactionFixture';
 import * as rightClickHandleAlias from './rightClickHandleAlias';
 import * as clickHandleReaction from '../clickHandleReaction';
 
@@ -51,7 +52,6 @@ describe('onMapRightClick', () => {
 
   it('calls rightClickHandleAlias if feature type is ALIAS', async () => {
     const dispatch = jest.fn();
-    const modelElement = modelElementsFixture.content[0];
     jest.spyOn(mapInstance, 'getAllLayers').mockImplementation((): Layer<Source>[] => {
       return [vectorLayer];
     });
@@ -62,9 +62,15 @@ describe('onMapRightClick', () => {
       return vectorSource;
     });
     jest.spyOn(vectorSource, 'getClosestFeatureToCoordinate').mockImplementation((): Feature => {
-      return new Feature({ id: modelElement.id, type: FEATURE_TYPE.ALIAS });
+      return new Feature({ id: modelElementFixture.id, type: FEATURE_TYPE.ALIAS });
     });
-    await onMapRightClick(mapSize, modelId, dispatch, [modelElement])(event, mapInstance);
+    await onMapRightClick(
+      mapSize,
+      modelId,
+      dispatch,
+      [modelElementFixture],
+      [],
+    )(event, mapInstance);
 
     expect(dispatch).toHaveBeenCalledWith(updateLastRightClick(expect.any(Object)));
     expect(dispatch).toHaveBeenCalledWith(openContextMenu(event.pixel));
@@ -85,7 +91,13 @@ describe('onMapRightClick', () => {
     jest.spyOn(vectorSource, 'getClosestFeatureToCoordinate').mockImplementation((): Feature => {
       return new Feature({ id: 1, type: FEATURE_TYPE.REACTION });
     });
-    await onMapRightClick(mapSize, modelId, dispatch, [])(event, mapInstance);
+    await onMapRightClick(
+      mapSize,
+      modelId,
+      dispatch,
+      [],
+      [{ ...newReactionFixture, id: 1 }],
+    )(event, mapInstance);
 
     expect(dispatch).toHaveBeenCalledWith(updateLastRightClick(expect.any(Object)));
     expect(dispatch).toHaveBeenCalledWith(openContextMenu(event.pixel));
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts
index ae01e642..cb141e34 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts
@@ -9,13 +9,13 @@ import { FEATURE_TYPE } from '@/constants/features';
 import VectorLayer from 'ol/layer/Vector';
 import VectorSource from 'ol/source/Vector';
 import { openContextMenu } from '@/redux/contextMenu/contextMenu.slice';
-import { ModelElement } from '@/types/models';
+import { ModelElement, NewReaction } from '@/types/models';
 import { rightClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias';
 import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
 
 /* prettier-ignore */
 export const onMapRightClick =
-  (mapSize: MapSize, modelId: number, dispatch: AppDispatch, modelElements: Array<ModelElement>) =>
+  (mapSize: MapSize, modelId: number, dispatch: AppDispatch, modelElements: Array<ModelElement>, reactions: Array<NewReaction>) =>
     async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => {
 
       const [lng, lat] = toLonLat(coordinate);
@@ -50,6 +50,6 @@ export const onMapRightClick =
         }
         await rightClickHandleAlias(dispatch)(id, modelElement);
       } else if (type === FEATURE_TYPE.REACTION) {
-        await clickHandleReaction(dispatch)(foundFeature, modelId);
+        clickHandleReaction(dispatch)(modelElements, reactions, id,  modelId);
       }
     };
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts
index 2a0af8ea..05993a81 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts
@@ -15,6 +15,7 @@ import { Coordinate } from 'ol/coordinate';
 import { Pixel } from 'ol/pixel';
 import { onMapRightClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick';
 import { modelElementsSelector } from '@/redux/modelElements/modelElements.selector';
+import { newReactionsDataSelector } from '@/redux/newReactions/newReactions.selectors';
 
 interface UseOlMapVectorListenersInput {
   mapInstance: MapInstance;
@@ -25,6 +26,7 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners
   const modelId = useSelector(currentModelIdSelector);
   const isResultDrawerOpen = useSelector(resultDrawerOpen);
   const modelElements = useSelector(modelElementsSelector);
+  const reactions = useSelector(newReactionsDataSelector);
   const dispatch = useAppDispatch();
   const coordinate = useRef<Coordinate>([]);
   const pixel = useRef<Pixel>([]);
@@ -33,13 +35,21 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners
   const vectorRendering = useAppSelector(vectorRenderingSelector);
 
   const handleMapLeftClick = useDebouncedCallback(
-    onMapLeftClick(mapSize, modelId, dispatch, isResultDrawerOpen, comments),
+    onMapLeftClick(
+      mapSize,
+      modelId,
+      dispatch,
+      isResultDrawerOpen,
+      comments,
+      modelElements?.content || [],
+      reactions,
+    ),
     OPTIONS.clickPersistTime,
     { leading: false },
   );
 
   const handleRightClick = useDebouncedCallback(
-    onMapRightClick(mapSize, modelId, dispatch, modelElements?.content || []),
+    onMapRightClick(mapSize, modelId, dispatch, modelElements?.content || [], reactions),
     OPTIONS.clickPersistTime,
     {
       leading: false,
diff --git a/src/redux/bioEntity/bioEntity.reducers.ts b/src/redux/bioEntity/bioEntity.reducers.ts
index deadfae3..f5f4f94c 100644
--- a/src/redux/bioEntity/bioEntity.reducers.ts
+++ b/src/redux/bioEntity/bioEntity.reducers.ts
@@ -1,7 +1,7 @@
 import { DEFAULT_ERROR } from '@/constants/errors';
 import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit';
 import { getBioEntityById } from '@/redux/bioEntity/thunks/getBioEntity';
-import { BioEntityContent } from '@/types/models';
+import { BioEntity, BioEntityContent } from '@/types/models';
 import { BIOENTITY_SUBMAP_CONNECTIONS_INITIAL_STATE } from './bioEntity.constants';
 import { getBioEntity, getMultiBioEntity } from './bioEntity.thunks';
 import { BioEntityContentsState } from './bioEntity.types';
@@ -136,3 +136,23 @@ export const setBioEntityContentsReducer = (
   ];
   state.loading = 'succeeded';
 };
+
+export const setMultipleBioEntityContentsReducer = (
+  state: BioEntityContentsState,
+  action: PayloadAction<Array<BioEntity>>,
+): void => {
+  state.data = [
+    {
+      data: action.payload.map(bioEntity => {
+        return {
+          bioEntity,
+          perfect: true,
+        };
+      }),
+      loading: 'succeeded',
+      error: DEFAULT_ERROR,
+      searchQueryElement: 'asd',
+    },
+  ];
+  state.loading = 'succeeded';
+};
diff --git a/src/redux/bioEntity/bioEntity.slice.ts b/src/redux/bioEntity/bioEntity.slice.ts
index 40f2bbb9..dbd2093c 100644
--- a/src/redux/bioEntity/bioEntity.slice.ts
+++ b/src/redux/bioEntity/bioEntity.slice.ts
@@ -6,6 +6,7 @@ import {
   getMultiBioEntityContentsReducer,
   getSubmapConnectionsBioEntityReducer,
   setBioEntityContentsReducer,
+  setMultipleBioEntityContentsReducer,
   toggleIsContentTabOpenedReducer,
 } from './bioEntity.reducers';
 
@@ -16,6 +17,7 @@ export const bioEntityContentsSlice = createSlice({
     clearBioEntitiesData: clearBioEntitiesDataReducer,
     toggleIsContentTabOpened: toggleIsContentTabOpenedReducer,
     setBioEntityContents: setBioEntityContentsReducer,
+    setMultipleBioEntityContents: setMultipleBioEntityContentsReducer,
   },
   extraReducers: builder => {
     getBioEntityContentsReducer(builder);
@@ -24,7 +26,11 @@ export const bioEntityContentsSlice = createSlice({
   },
 });
 
-export const { clearBioEntitiesData, toggleIsContentTabOpened, setBioEntityContents } =
-  bioEntityContentsSlice.actions;
+export const {
+  clearBioEntitiesData,
+  toggleIsContentTabOpened,
+  setBioEntityContents,
+  setMultipleBioEntityContents,
+} = bioEntityContentsSlice.actions;
 
 export default bioEntityContentsSlice.reducer;
diff --git a/src/redux/entityNumber/entityNumber.reducers.ts b/src/redux/entityNumber/entityNumber.reducers.ts
index dd26edb9..15662170 100644
--- a/src/redux/entityNumber/entityNumber.reducers.ts
+++ b/src/redux/entityNumber/entityNumber.reducers.ts
@@ -20,7 +20,6 @@ export const addNumbersToEntityNumberDataReducer = (
   const newEntityNumber: EntityNumber = Object.fromEntries(
     uniqueIds.map((id, index) => [id, lastNumber + index]),
   );
-
   state.data = {
     ...newEntityNumber,
     ...state.data,
diff --git a/src/redux/newReactions/newReactions.selectors.ts b/src/redux/newReactions/newReactions.selectors.ts
index fd9978fd..4dc2babe 100644
--- a/src/redux/newReactions/newReactions.selectors.ts
+++ b/src/redux/newReactions/newReactions.selectors.ts
@@ -7,8 +7,3 @@ export const newReactionsDataSelector = createSelector(
   newReactionsSelector,
   reactions => reactions.data || [],
 );
-
-// export const newReactionsReactionByIdSelector = createSelector(
-//   newReactionsDataSelector,
-//   reactions => reactions.find(reaction => reaction.id === id) || [],
-// );
diff --git a/src/redux/reactions/reactions.reducers.ts b/src/redux/reactions/reactions.reducers.ts
index 2ef4a163..848b4ab7 100644
--- a/src/redux/reactions/reactions.reducers.ts
+++ b/src/redux/reactions/reactions.reducers.ts
@@ -1,4 +1,5 @@
-import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
+import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit';
+import { Reaction } from '@/types/models';
 import { REACTIONS_INITIAL_STATE } from './reactions.constants';
 import { getReactionsByIds } from './reactions.thunks';
 import { ReactionsState } from './reactions.types';
@@ -26,3 +27,11 @@ export const resetReactionsDataReducer = (state: ReactionsState): void => {
   state.error = REACTIONS_INITIAL_STATE.error;
   state.loading = REACTIONS_INITIAL_STATE.loading;
 };
+
+export const setReactionsReducer = (
+  state: ReactionsState,
+  action: PayloadAction<Array<Reaction>>,
+): void => {
+  state.data = action.payload;
+  state.loading = 'succeeded';
+};
diff --git a/src/redux/reactions/reactions.slice.ts b/src/redux/reactions/reactions.slice.ts
index f97e9050..97cce842 100644
--- a/src/redux/reactions/reactions.slice.ts
+++ b/src/redux/reactions/reactions.slice.ts
@@ -1,18 +1,23 @@
 import { createSlice } from '@reduxjs/toolkit';
 import { REACTIONS_INITIAL_STATE } from './reactions.constants';
-import { getReactionsReducer, resetReactionsDataReducer } from './reactions.reducers';
+import {
+  getReactionsReducer,
+  resetReactionsDataReducer,
+  setReactionsReducer,
+} from './reactions.reducers';
 
 export const reactionsSlice = createSlice({
   name: 'reactions',
   initialState: REACTIONS_INITIAL_STATE,
   reducers: {
     resetReactionsData: resetReactionsDataReducer,
+    setReactions: setReactionsReducer,
   },
   extraReducers: builder => {
     getReactionsReducer(builder);
   },
 });
 
-export const { resetReactionsData } = reactionsSlice.actions;
+export const { resetReactionsData, setReactions } = reactionsSlice.actions;
 
 export default reactionsSlice.reducer;
diff --git a/src/utils/bioEntity/mapReactionToBioEntity.ts b/src/utils/bioEntity/mapReactionToBioEntity.ts
new file mode 100644
index 00000000..0aab81b7
--- /dev/null
+++ b/src/utils/bioEntity/mapReactionToBioEntity.ts
@@ -0,0 +1,19 @@
+import { BioEntity, NewReaction } from '@/types/models';
+
+export function mapReactionToBioEntity(reaction: NewReaction): BioEntity {
+  return {
+    id: reaction.id,
+    name: reaction.name,
+    model: reaction.model,
+    elementId: reaction.elementId,
+    references: reaction.references,
+    z: reaction.z,
+    notes: reaction.notes,
+    symbol: reaction.symbol,
+    visibilityLevel: reaction.visibilityLevel,
+    synonyms: reaction.synonyms,
+    abbreviation: reaction.abbreviation,
+    formula: reaction.formula,
+    sboTerm: reaction.sboTerm,
+  } as BioEntity;
+}
diff --git a/src/utils/reaction/ReactionTypeEnum.ts b/src/utils/reaction/ReactionTypeEnum.ts
new file mode 100644
index 00000000..a10ec320
--- /dev/null
+++ b/src/utils/reaction/ReactionTypeEnum.ts
@@ -0,0 +1,30 @@
+enum ReactionTypeEnum {
+  'SBO:0000013' = 'Catalysis',
+  'SBO:0000180' = 'Dissociation',
+  'SBO:0000177' = 'Heterodimer association',
+  'SBO:0000537' = 'Inhibition',
+  'SBO:0000205' = 'Known Transition omitted',
+  'SBO:0000594' = 'Modulation',
+  'SBO:0000407' = 'Negative influence',
+  'SBO:0000459' = 'Physical stimulation',
+  'SBO:0000171' = 'Positive influence',
+  'SBO:0000632' = 'Reduced modulation',
+  'SBO:0000411' = 'Reduced physical stimulation',
+  'SBO:0000533' = 'Reduced trigger',
+  'SBO:0000176' = 'State transition',
+  'SBO:0000183' = 'Transcription',
+  'SBO:0000184' = 'Translation',
+  'SBO:0000185' = 'Transport',
+  'SBO:0000461' = 'Trigger',
+  'SBO:0000178' = 'Truncation',
+  'SBO:0000462' = 'Unknown catalysis',
+  'SBO:0000536' = 'Unknown inhibition',
+  'SBO:0000169' = 'Unknown negative influence',
+  'SBO:0000172' = 'Unknown positive influence',
+  'SBO:0000631' = 'Unknown reduced modulation',
+  'SBO:0000170' = 'Unknown reduced physical stimulation',
+  'SBO:0000534' = 'Unknown reduced trigger',
+  'SBO:0000396' = 'Unknown transition',
+}
+
+export default ReactionTypeEnum;
diff --git a/src/utils/reaction/mapNewReactionToReaction.test.ts b/src/utils/reaction/mapNewReactionToReaction.test.ts
new file mode 100644
index 00000000..23097b83
--- /dev/null
+++ b/src/utils/reaction/mapNewReactionToReaction.test.ts
@@ -0,0 +1,193 @@
+/* eslint-disable no-magic-numbers */
+import mapNewReactionToReaction from '@/utils/reaction/mapNewReactionToReaction';
+
+describe('mapNewReactionToReaction', () => {
+  const newReaction = {
+    id: 31141,
+    notes: '',
+    idReaction: 're22',
+    name: '',
+    reversible: false,
+    symbol: null,
+    abbreviation: null,
+    formula: null,
+    mechanicalConfidenceScore: null,
+    lowerBound: null,
+    upperBound: null,
+    subsystem: null,
+    geneProteinReaction: null,
+    visibilityLevel: '',
+    z: 45,
+    synonyms: [],
+    model: 137,
+    kinetics: null,
+    line: {
+      id: 109668,
+      width: 1,
+      color: {
+        alpha: 255,
+        rgb: -16777216,
+      },
+      z: 0,
+      segments: [
+        {
+          x1: 149.31765717927775,
+          y1: 319.13724818355684,
+          x2: 142.5553586937381,
+          y2: 314.86275181644316,
+        },
+      ],
+      startArrow: {
+        arrowType: 'NONE',
+        angle: 2.748893571891069,
+        lineType: 'SOLID',
+        length: 15,
+      },
+      endArrow: {
+        arrowType: 'NONE',
+        angle: 2.748893571891069,
+        lineType: 'SOLID',
+        length: 15,
+      },
+      lineType: 'SOLID',
+    },
+    processCoordinates: null,
+    modifiers: [],
+    products: [
+      {
+        id: 85169,
+        line: {
+          id: 109670,
+          width: 1,
+          color: {
+            alpha: 255,
+            rgb: -16777216,
+          },
+          z: 0,
+          segments: [
+            {
+              x1: 142.5553586937381,
+              y1: 314.86275181644316,
+              x2: 122.2063492063492,
+              y2: 302,
+            },
+          ],
+          startArrow: {
+            arrowType: 'NONE',
+            angle: 2.748893571891069,
+            lineType: 'SOLID',
+            length: 15,
+          },
+          endArrow: {
+            arrowType: 'OPEN',
+            angle: 2.748893571891069,
+            lineType: 'SOLID',
+            length: 15,
+          },
+          lineType: 'SOLID',
+        },
+        stoichiometry: null,
+        element: 58886,
+      },
+    ],
+    reactants: [
+      {
+        id: 85168,
+        line: {
+          id: 109669,
+          width: 1,
+          color: {
+            alpha: 255,
+            rgb: -16777216,
+          },
+          z: 0,
+          segments: [
+            {
+              x1: 169.66666666666666,
+              y1: 332,
+              x2: 149.31765717927775,
+              y2: 319.13724818355684,
+            },
+          ],
+          startArrow: {
+            arrowType: 'NONE',
+            angle: 2.748893571891069,
+            lineType: 'SOLID',
+            length: 15,
+          },
+          endArrow: {
+            arrowType: 'NONE',
+            angle: 2.748893571891069,
+            lineType: 'SOLID',
+            length: 15,
+          },
+          lineType: 'SOLID',
+        },
+        stoichiometry: null,
+        element: 58872,
+      },
+    ],
+    operators: [],
+    elementId: 're22',
+    references: [],
+    sboTerm: 'SBO:0000171',
+  };
+  const expectedReaction = {
+    centerPoint: {
+      x: 0,
+      y: 0,
+    },
+    hierarchyVisibilityLevel: '',
+    id: 31141,
+    kineticLaw: null,
+    lines: [
+      {
+        start: {
+          x: 149.31765717927775,
+          y: 319.13724818355684,
+        },
+        end: {
+          x: 142.5553586937381,
+          y: 314.86275181644316,
+        },
+        type: '',
+      },
+      {
+        start: {
+          x: 142.5553586937381,
+          y: 314.86275181644316,
+        },
+        end: {
+          x: 122.2063492063492,
+          y: 302,
+        },
+        type: '',
+      },
+      {
+        start: {
+          x: 169.66666666666666,
+          y: 332,
+        },
+        end: {
+          x: 149.31765717927775,
+          y: 319.13724818355684,
+        },
+        type: '',
+      },
+    ],
+    modelId: 137,
+    modifiers: [],
+    name: '',
+    notes: '',
+    products: [],
+    reactants: [],
+    reactionId: 're22',
+    references: [],
+    type: 'Positive influence',
+  };
+
+  it('should return correct reaction object from new reaction object', () => {
+    const result = mapNewReactionToReaction(newReaction);
+    expect(result).toEqual(expectedReaction);
+  });
+});
diff --git a/src/utils/reaction/mapNewReactionToReaction.ts b/src/utils/reaction/mapNewReactionToReaction.ts
new file mode 100644
index 00000000..a5fbd0e4
--- /dev/null
+++ b/src/utils/reaction/mapNewReactionToReaction.ts
@@ -0,0 +1,45 @@
+/* eslint-disable no-magic-numbers */
+import { NewReaction, Reaction, ReactionLine } from '@/types/models';
+import ReactionTypeEnum from '@/utils/reaction/ReactionTypeEnum';
+
+type ReactionTypeKey = keyof typeof ReactionTypeEnum;
+
+export default function mapNewReactionToReaction(newReaction: NewReaction): Reaction {
+  const lines: Array<ReactionLine> = [];
+  let start;
+  let end;
+  newReaction.line.segments.forEach(segment => {
+    start = { x: segment.x1, y: segment.y1 };
+    end = { x: segment.x2, y: segment.y2 };
+    lines.push({ start, end, type: '' });
+  });
+  [
+    ...newReaction.products,
+    ...newReaction.reactants,
+    ...newReaction.modifiers,
+    ...newReaction.operators,
+  ].forEach(element => {
+    element.line.segments.forEach(segment => {
+      start = { x: segment.x1, y: segment.y1 };
+      end = { x: segment.x2, y: segment.y2 };
+      lines.push({ start, end, type: '' });
+    });
+  });
+
+  return {
+    centerPoint: { x: 0, y: 0 },
+    hierarchyVisibilityLevel: '',
+    id: newReaction.id,
+    kineticLaw: null,
+    lines,
+    modelId: newReaction.model,
+    modifiers: [],
+    name: '',
+    notes: newReaction.notes,
+    products: [],
+    reactants: [],
+    reactionId: newReaction.idReaction,
+    references: newReaction.references,
+    type: ReactionTypeEnum[newReaction.sboTerm as ReactionTypeKey],
+  };
+}
-- 
GitLab


From 23f08c1e7345a5590de9d218aa6b6adf16271e4e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Tue, 12 Nov 2024 13:19:40 +0100
Subject: [PATCH 03/29] refactor(vector-map): move vector layer type name
 string to constant

---
 .../MapViewer/MapViewerVector/MapViewerVector.constants.ts   | 2 ++
 .../mouseClick/mouseRightClick/onMapRightClick.test.ts       | 3 ++-
 .../listeners/mouseClick/mouseRightClick/onMapRightClick.ts  | 3 ++-
 .../utils/config/reactionsLayer/useOlMapReactionsLayer.ts    | 3 ++-
 src/redux/newReactions/newReactions.selectors.ts             | 5 -----
 5 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
index 4ac8a6fb..9a036ac1 100644
--- a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
@@ -1,5 +1,7 @@
 import { Color, ShapeRelAbs, ShapeRelAbsBezierPoint } from '@/types/models';
 
+export const VECTOR_MAP_LAYER_TYPE = 'vectorMapLayer';
+
 export const WHITE_COLOR: Color = {
   alpha: 255,
   rgb: 16777215,
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts
index 362df7ae..a96f6601 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts
@@ -10,6 +10,7 @@ import VectorSource from 'ol/source/Vector';
 import { Feature } from 'ol';
 import { FEATURE_TYPE } from '@/constants/features';
 import { modelElementsFixture } from '@/models/fixtures/modelElementsFixture';
+import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import * as rightClickHandleAlias from './rightClickHandleAlias';
 import * as clickHandleReaction from '../clickHandleReaction';
 
@@ -45,7 +46,7 @@ describe('onMapRightClick', () => {
     vectorLayer = new VectorLayer({
       source: vectorSource,
     });
-    vectorLayer.set('type', 'vectorMapLayer');
+    vectorLayer.set('type', VECTOR_MAP_LAYER_TYPE);
     jest.clearAllMocks();
   });
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts
index ae01e642..c4673a83 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts
@@ -12,6 +12,7 @@ import { openContextMenu } from '@/redux/contextMenu/contextMenu.slice';
 import { ModelElement } from '@/types/models';
 import { rightClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias';
 import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
+import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 
 /* prettier-ignore */
 export const onMapRightClick =
@@ -25,7 +26,7 @@ export const onMapRightClick =
       let foundFeature: Feature | undefined;
       mapInstance.getAllLayers().forEach(layer => {
         if(layer.isVisible() && layer instanceof VectorLayer) {
-          if (layer.get('type') === 'vectorMapLayer') {
+          if (layer.get('type') === VECTOR_MAP_LAYER_TYPE) {
             const source = layer.getSource();
             if (source instanceof VectorSource) {
               foundFeature = source.getClosestFeatureToCoordinate(coordinate, (feature) => {
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index 5fbc2ae0..00ff8cb8 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -28,6 +28,7 @@ import CompartmentPathway from '@/components/Map/MapViewer/MapViewerVector/utils
 import Reaction from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction';
 import { newReactionsDataSelector } from '@/redux/newReactions/newReactions.selectors';
 import { getNewReactions } from '@/redux/newReactions/newReactions.thunks';
+import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 
 export const useOlMapReactionsLayer = ({
   mapInstance,
@@ -186,7 +187,7 @@ export const useOlMapReactionsLayer = ({
     const vectorLayer = new VectorLayer({
       source: vectorSource,
     });
-    vectorLayer.set('type', 'vectorMapLayer');
+    vectorLayer.set('type', VECTOR_MAP_LAYER_TYPE);
     return vectorLayer;
   }, [vectorSource]);
 };
diff --git a/src/redux/newReactions/newReactions.selectors.ts b/src/redux/newReactions/newReactions.selectors.ts
index fd9978fd..4dc2babe 100644
--- a/src/redux/newReactions/newReactions.selectors.ts
+++ b/src/redux/newReactions/newReactions.selectors.ts
@@ -7,8 +7,3 @@ export const newReactionsDataSelector = createSelector(
   newReactionsSelector,
   reactions => reactions.data || [],
 );
-
-// export const newReactionsReactionByIdSelector = createSelector(
-//   newReactionsDataSelector,
-//   reactions => reactions.find(reaction => reaction.id === id) || [],
-// );
-- 
GitLab


From fd7315e2e83819974612f6a4facd044bf539a95b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Wed, 13 Nov 2024 16:27:06 +0100
Subject: [PATCH 04/29] feat(vector-map): implement rectangle and sumbap-link
 overlays functionality

---
 .../MapViewerVector/MapViewerVector.types.ts  |  5 ++
 .../reactionsLayer/useOlMapReactionsLayer.ts  | 42 ++++++++++--
 .../utils/shapes/elements/MapElement.test.ts  |  1 +
 .../utils/shapes/elements/MapElement.ts       | 56 +++++++++++++++-
 .../calculateOverlayDimensions.test.ts        | 47 +++++++++++++
 .../overlay/calculateOverlayDimensions.ts     | 20 ++++++
 .../findMatchingSubmapLinkRectangle.test.ts   | 66 +++++++++++++++++++
 .../findMatchingSubmapLinkRectangle.ts        | 19 ++++++
 .../utils/shapes/overlay/getOverlays.test.ts  | 60 +++++++++++++++++
 .../utils/shapes/overlay/getOverlays.ts       | 26 ++++++++
 .../overlay/groupOverlayEntities.test.ts      | 60 +++++++++++++++++
 .../shapes/overlay/groupOverlayEntities.ts    | 34 ++++++++++
 .../processOverlayGroupedElements.test.ts     | 55 ++++++++++++++++
 .../overlay/processOverlayGroupedElements.ts  | 35 ++++++++++
 .../overlay/sortElementOverlayByColor.test.ts | 51 ++++++++++++++
 .../overlay/sortElementOverlayByColor.ts      | 20 ++++++
 .../utils/shapes/style/getStyle.ts            |  8 +--
 .../createOverlayGeometryFeature.ts           |  5 +-
 .../overlaysLayer/useGetOverlayColor.ts       |  4 +-
 .../utils/config/useOlMapCommonLayers.test.ts |  7 --
 .../utils/config/useOlMapCommonLayers.ts      |  4 +-
 .../utils/config/useOlMapLayers.test.ts       |  8 +++
 .../MapViewer/utils/config/useOlMapLayers.ts  |  4 +-
 23 files changed, 613 insertions(+), 24 deletions(-)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/calculateOverlayDimensions.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/calculateOverlayDimensions.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/findMatchingSubmapLinkRectangle.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/findMatchingSubmapLinkRectangle.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/getOverlays.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/getOverlays.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/groupOverlayEntities.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/groupOverlayEntities.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/processOverlayGroupedElements.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/processOverlayGroupedElements.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/sortElementOverlayByColor.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/sortElementOverlayByColor.ts

diff --git a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts
index a896f4be..e7ccb7c9 100644
--- a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts
@@ -1,5 +1,6 @@
 import View from 'ol/View';
 import BaseLayer from 'ol/layer/Base';
+import { OverlayBioEntityRender } from '@/types/OLrendering';
 
 export type MapConfig = {
   view: View;
@@ -8,3 +9,7 @@ export type MapConfig = {
 
 export type VerticalAlign = 'TOP' | 'MIDDLE' | 'BOTTOM';
 export type HorizontalAlign = 'LEFT' | 'RIGHT' | 'CENTER' | 'END' | 'START';
+
+export type OverlayBioEntityGroupedElementsType = {
+  [id: string]: Array<OverlayBioEntityRender & { amount: number }>;
+};
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index 00ff8cb8..4e0b8bd0 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -29,6 +29,14 @@ import Reaction from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/re
 import { newReactionsDataSelector } from '@/redux/newReactions/newReactions.selectors';
 import { getNewReactions } from '@/redux/newReactions/newReactions.thunks';
 import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
+import {
+  getOverlayOrderSelector,
+  overlayBioEntitiesForCurrentModelSelector,
+} from '@/redux/overlayBioEntity/overlayBioEntity.selector';
+import { groupBy } from '@/utils/array/groupBy';
+import { useGetOverlayColor } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import getOverlays from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/getOverlays';
 
 export const useOlMapReactionsLayer = ({
   mapInstance,
@@ -36,18 +44,28 @@ export const useOlMapReactionsLayer = ({
   mapInstance: MapInstance;
 }): VectorLayer<VectorSource<Feature>> => {
   const dispatch = useAppDispatch();
+
+  const currentModelId = useSelector(currentModelIdSelector);
   const modelElements = useSelector(modelElementsSelector);
   const modelReactions = useSelector(newReactionsDataSelector);
-  const currentModelId = useSelector(currentModelIdSelector);
+  const shapes = useSelector(bioShapesSelector);
+  const lineTypes = useSelector(lineTypesSelector);
+  const arrowTypes = useSelector(arrowTypesSelector);
+  const overlaysOrder = useSelector(getOverlayOrderSelector);
+  const { getOverlayBioEntityColorByAvailableProperties } = useGetOverlayColor();
+
+  const pointToProjection = usePointToProjection();
+
   useEffect(() => {
     dispatch(getModelElements(currentModelId));
     dispatch(getNewReactions(currentModelId));
   }, [currentModelId, dispatch]);
 
-  const pointToProjection = usePointToProjection();
-  const shapes = useSelector(bioShapesSelector);
-  const lineTypes = useSelector(lineTypesSelector);
-  const arrowTypes = useSelector(arrowTypesSelector);
+  const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
+  const groupedOverlays = useMemo(() => {
+    const grouped = groupBy(bioEntities, bioEntity => bioEntity.id.toString());
+    return getOverlays(grouped, getOverlayBioEntityColorByAvailableProperties);
+  }, [bioEntities, getOverlayBioEntityColorByAvailableProperties]);
 
   const reactions = useMemo(() => {
     return modelReactions.map(reaction => {
@@ -164,12 +182,24 @@ export const useOlMapReactionsLayer = ({
             modifications: element.modificationResidues,
             lineTypes,
             bioShapes: shapes,
+            overlays: groupedOverlays[element.id],
+            overlaysOrder,
+            getOverlayColor: getOverlayBioEntityColorByAvailableProperties,
           }),
         );
       }
     });
     return validElements;
-  }, [modelElements, shapes, pointToProjection, mapInstance, lineTypes]);
+  }, [
+    modelElements,
+    shapes,
+    pointToProjection,
+    mapInstance,
+    lineTypes,
+    groupedOverlays,
+    overlaysOrder,
+    getOverlayBioEntityColorByAvailableProperties,
+  ]);
 
   const features = useMemo(() => {
     const reactionsFeatures = reactions.flat();
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
index 64c1f92d..71c5c3c2 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
@@ -60,6 +60,7 @@ describe('MapElement', () => {
       nameHorizontalAlign: 'CENTER',
       pointToProjection: jest.fn(),
       mapInstance,
+      getOverlayColor: (): string => '#ffffff',
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
index dae35c65..94d5f96f 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
@@ -22,6 +22,11 @@ import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shape
 import getShapePolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getShapePolygon';
 import { BioShapesDict, LineTypeDict } from '@/redux/shapes/shapes.types';
 import { FEATURE_TYPE } from '@/constants/features';
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import { getPolygonLatitudeCoordinates } from '@/components/Map/MapViewer/utils/config/overlaysLayer/getPolygonLatitudeCoordinates';
+import { ZERO } from '@/constants/common';
+import { OverlayOrder } from '@/redux/overlayBioEntity/overlayBioEntity.utils';
+import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
 
 export type MapElementProps = {
   id: number;
@@ -51,6 +56,9 @@ export type MapElementProps = {
   bioShapes?: BioShapesDict;
   lineTypes?: LineTypeDict;
   modifications?: Array<Modification>;
+  overlays?: Array<OverlayBioEntityRender>;
+  overlaysOrder?: Array<OverlayOrder>;
+  getOverlayColor: GetOverlayBioEntityColorByAvailableProperties;
 };
 
 export default class MapElement extends BaseMultiPolygon {
@@ -72,6 +80,12 @@ export default class MapElement extends BaseMultiPolygon {
 
   lineDash: Array<number> = [];
 
+  overlays: Array<OverlayBioEntityRender> = [];
+
+  overlaysOrder: Array<OverlayOrder> = [];
+
+  getOverlayColor: GetOverlayBioEntityColorByAvailableProperties;
+
   constructor({
     id,
     shapes,
@@ -100,6 +114,9 @@ export default class MapElement extends BaseMultiPolygon {
     bioShapes = {},
     lineTypes = {},
     modifications = [],
+    overlays = [],
+    overlaysOrder = [],
+    getOverlayColor,
   }: MapElementProps) {
     super({
       type: FEATURE_TYPE.ALIAS,
@@ -130,6 +147,9 @@ export default class MapElement extends BaseMultiPolygon {
     this.bioShapes = bioShapes;
     this.lineTypes = lineTypes;
     this.modifications = modifications;
+    this.overlays = overlays;
+    this.overlaysOrder = overlaysOrder;
+    this.getOverlayColor = getOverlayColor;
     this.createPolygons();
     this.drawText();
     this.drawMultiPolygonFeature(mapInstance);
@@ -160,6 +180,7 @@ export default class MapElement extends BaseMultiPolygon {
       }
       this.drawElementPolygon(homodimerShift, homodimerOffset);
     }
+    this.drawOverlays();
   }
 
   drawModification(modification: Modification, shapes: Array<Shape>): void {
@@ -250,7 +271,7 @@ export default class MapElement extends BaseMultiPolygon {
       const elementStyle = getStyle({
         geometry: elementPolygon,
         borderColor: this.borderColor,
-        fillColor: this.fillColor,
+        fillColor: this.overlays.length ? undefined : this.fillColor,
         lineWidth: this.lineWidth,
         lineDash: this.lineDash,
         zIndex: this.zIndex,
@@ -260,4 +281,37 @@ export default class MapElement extends BaseMultiPolygon {
       this.styles.push(elementStyle);
     });
   }
+
+  drawOverlays(): void {
+    this.overlays.forEach(entity => {
+      if (entity.value === Infinity) {
+        return;
+      }
+      const { xMin, xMax } = getPolygonLatitudeCoordinates({
+        width: entity.width,
+        nOverlays: this.overlaysOrder.length,
+        xMin: entity.x1,
+        overlayIndexBasedOnOrder:
+          this.overlaysOrder.find(({ id }) => id === entity.overlayId)?.index || ZERO,
+      });
+      const color = this.getOverlayColor(entity);
+      const polygon = new Polygon([
+        [
+          this.pointToProjection({ x: xMin, y: entity.y1 }),
+          this.pointToProjection({ x: xMax, y: entity.y1 }),
+          this.pointToProjection({ x: xMax, y: entity.y2 }),
+          this.pointToProjection({ x: xMin, y: entity.y2 }),
+        ],
+      ]);
+      const style = getStyle({
+        geometry: polygon,
+        borderColor: color,
+        fillColor: color,
+        zIndex: this.zIndex,
+      });
+      this.polygons.push(polygon);
+      this.lineWidths.push(1);
+      this.styles.push(style);
+    });
+  }
 }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/calculateOverlayDimensions.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/calculateOverlayDimensions.test.ts
new file mode 100644
index 00000000..e328d4e4
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/calculateOverlayDimensions.test.ts
@@ -0,0 +1,47 @@
+/* eslint-disable no-magic-numbers */
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import calculateOverlayDimensions from './calculateOverlayDimensions';
+
+describe('calculateOverlayDimensions', () => {
+  it('should calculate overlay dimensions and update y1, y2, and height', () => {
+    const overlay: OverlayBioEntityRender & { amount: number } = {
+      amount: 3,
+      color: null,
+      height: 100,
+      hexColor: '#0000001a',
+      id: '3',
+      modelId: 0,
+      overlayId: 1,
+      type: 'submap-link',
+      value: -0.43,
+      width: 100,
+      x1: 200,
+      x2: 300,
+      y1: 750,
+      y2: 700,
+    };
+    const entityOverlays: Array<OverlayBioEntityRender> = [
+      {
+        color: null,
+        height: 100,
+        hexColor: '#0000001a',
+        id: '3',
+        modelId: 0,
+        overlayId: 1,
+        type: 'submap-link',
+        value: -0.43,
+        width: 100,
+        x1: 200,
+        x2: 300,
+        y1: 750,
+        y2: 700,
+      },
+    ];
+
+    const index = 1;
+    const result = calculateOverlayDimensions(overlay, index, 4, 100, entityOverlays);
+
+    expect(result.height).toEqual(75);
+    expect(result.y1).toEqual(entityOverlays[index - 1].y1 + 75);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/calculateOverlayDimensions.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/calculateOverlayDimensions.ts
new file mode 100644
index 00000000..7ac9819c
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/calculateOverlayDimensions.ts
@@ -0,0 +1,20 @@
+/* eslint-disable no-magic-numbers */
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+
+export default function calculateOverlayDimensions(
+  overlay: OverlayBioEntityRender & { amount: number },
+  index: number,
+  totalAmount: number,
+  totalHeight: number,
+  entityOverlays: Array<OverlayBioEntityRender>,
+): OverlayBioEntityRender {
+  const ratio = overlay.amount / totalAmount;
+  const overlayHeight = ratio * totalHeight;
+  const overlayEntity = { ...overlay, height: overlayHeight };
+  if (index !== 0) {
+    overlayEntity.y2 = entityOverlays[index - 1].y1;
+  }
+  overlayEntity.y1 = overlayEntity.y2 + overlayHeight;
+
+  return overlayEntity;
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/findMatchingSubmapLinkRectangle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/findMatchingSubmapLinkRectangle.test.ts
new file mode 100644
index 00000000..e19c1bfc
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/findMatchingSubmapLinkRectangle.test.ts
@@ -0,0 +1,66 @@
+/* eslint-disable no-magic-numbers */
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import findMatchingSubmapLinkRectangle from './findMatchingSubmapLinkRectangle';
+
+describe('findMatchingSubmapLinkRectangle', () => {
+  const elements: Array<OverlayBioEntityRender & { amount: number }> = [
+    {
+      amount: 1,
+      color: null,
+      height: 50,
+      hexColor: '#0000001a',
+      id: '3',
+      modelId: 0,
+      overlayId: 1,
+      type: 'submap-link',
+      value: -0.43,
+      width: 100,
+      x1: 200,
+      x2: 300,
+      y1: 750,
+      y2: 700,
+    },
+  ];
+
+  it('should find a matching submap link rectangle by value or color', () => {
+    const overlayBioEntity: OverlayBioEntityRender = {
+      color: null,
+      height: 50,
+      hexColor: '#0000001a',
+      id: '3',
+      modelId: 0,
+      overlayId: 1,
+      type: 'submap-link',
+      value: -0.43,
+      width: 100,
+      x1: 200,
+      x2: 300,
+      y1: 750,
+      y2: 700,
+    };
+
+    const result = findMatchingSubmapLinkRectangle(elements, overlayBioEntity);
+    expect(result).toEqual(elements[0]);
+  });
+
+  it('should return undefined if no matching element is found', () => {
+    const overlayBioEntity: OverlayBioEntityRender = {
+      color: null,
+      height: 50,
+      hexColor: '#0000001a',
+      id: '3',
+      modelId: 0,
+      overlayId: 1,
+      type: 'submap-link',
+      value: 0.21,
+      width: 100,
+      x1: 200,
+      x2: 300,
+      y1: 750,
+      y2: 700,
+    };
+
+    const result = findMatchingSubmapLinkRectangle(elements, overlayBioEntity);
+    expect(result).toBeUndefined();
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/findMatchingSubmapLinkRectangle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/findMatchingSubmapLinkRectangle.ts
new file mode 100644
index 00000000..25305cc3
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/findMatchingSubmapLinkRectangle.ts
@@ -0,0 +1,19 @@
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+
+export default function findMatchingSubmapLinkRectangle(
+  elements: Array<OverlayBioEntityRender & { amount: number }>,
+  overlayBioEntity: OverlayBioEntityRender,
+): (OverlayBioEntityRender & { amount: number }) | undefined {
+  return elements.find(element => {
+    const hasAllRequiredValueProperties = element.value && overlayBioEntity.value;
+    const isValueEqual = hasAllRequiredValueProperties && element.value === overlayBioEntity.value;
+
+    const hasAllRequiredColorProperties = element.color && overlayBioEntity.color;
+    const isColorEqual =
+      hasAllRequiredColorProperties &&
+      element.color?.alpha === overlayBioEntity?.color?.alpha &&
+      element.color?.rgb === overlayBioEntity?.color?.rgb;
+
+    return Boolean(isValueEqual || isColorEqual);
+  });
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/getOverlays.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/getOverlays.test.ts
new file mode 100644
index 00000000..e9fef8cf
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/getOverlays.test.ts
@@ -0,0 +1,60 @@
+/* eslint-disable no-magic-numbers */
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import groupOverlayEntities from './groupOverlayEntities';
+
+describe('groupOverlays', () => {
+  it('should group overlay entities correctly by overlayId, value, and color', () => {
+    const overlayBioEntities: Array<OverlayBioEntityRender> = [
+      {
+        color: null,
+        height: 50,
+        hexColor: '#0000001a',
+        id: '1',
+        modelId: 0,
+        overlayId: 1,
+        type: 'submap-link',
+        value: 0.1,
+        width: 100,
+        x1: 1200,
+        x2: 1300,
+        y1: 550,
+        y2: 500,
+      },
+      {
+        color: null,
+        height: 50,
+        hexColor: '#0000001a',
+        id: '1',
+        modelId: 0,
+        overlayId: 1,
+        type: 'submap-link',
+        value: 0.1,
+        width: 100,
+        x1: 1200,
+        x2: 1300,
+        y1: 550,
+        y2: 500,
+      },
+      {
+        color: null,
+        height: 50,
+        hexColor: '#0000001a',
+        id: '3',
+        modelId: 0,
+        overlayId: 2,
+        type: 'submap-link',
+        value: -0.43,
+        width: 100,
+        x1: 200,
+        x2: 300,
+        y1: 750,
+        y2: 700,
+      },
+    ];
+
+    const result = groupOverlayEntities(overlayBioEntities);
+    expect(result['1'][0].amount).toBe(2);
+    expect(result['2'][0].amount).toBe(1);
+    expect(result['3']).toBeUndefined();
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/getOverlays.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/getOverlays.ts
new file mode 100644
index 00000000..8cc7c004
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/getOverlays.ts
@@ -0,0 +1,26 @@
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
+import groupOverlayEntities from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/groupOverlayEntities';
+import processOverlayGroupedElements from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/processOverlayGroupedElements';
+
+export default function getOverlays(
+  groupedOverlays: Record<string, Array<OverlayBioEntityRender>>,
+  getColor: GetOverlayBioEntityColorByAvailableProperties,
+): Record<string, Array<OverlayBioEntityRender>> {
+  const resultEntityOverlays: Record<string, Array<OverlayBioEntityRender>> = {};
+
+  Object.entries(groupedOverlays).forEach(([key, overlayBioEntities]) => {
+    const entityOverlays: Array<OverlayBioEntityRender> = [];
+    if (!resultEntityOverlays[key]) {
+      resultEntityOverlays[key] = [];
+    }
+
+    const groupedElements = groupOverlayEntities(overlayBioEntities);
+
+    processOverlayGroupedElements(groupedElements, entityOverlays, getColor);
+
+    resultEntityOverlays[key].push(...entityOverlays);
+  });
+
+  return resultEntityOverlays;
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/groupOverlayEntities.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/groupOverlayEntities.test.ts
new file mode 100644
index 00000000..1f58872b
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/groupOverlayEntities.test.ts
@@ -0,0 +1,60 @@
+/* eslint-disable no-magic-numbers */
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import groupOverlayEntities from './groupOverlayEntities';
+
+describe('groupOverlayEntities', () => {
+  it('should group overlay entities correctly by overlayId, value, and color', () => {
+    const overlayBioEntities: Array<OverlayBioEntityRender> = [
+      {
+        color: null,
+        height: 50,
+        hexColor: '#0000001a',
+        id: '1',
+        modelId: 0,
+        overlayId: 0,
+        type: 'submap-link',
+        value: 0.1,
+        width: 100,
+        x1: 1200,
+        x2: 1300,
+        y1: 550,
+        y2: 500,
+      },
+      {
+        color: null,
+        height: 50,
+        hexColor: '#0000001a',
+        id: '2',
+        modelId: 0,
+        overlayId: 0,
+        type: 'submap-link',
+        value: 0.1,
+        width: 100,
+        x1: 1200,
+        x2: 1300,
+        y1: 550,
+        y2: 500,
+      },
+      {
+        color: null,
+        height: 50,
+        hexColor: '#0000001a',
+        id: '3',
+        modelId: 0,
+        overlayId: 1,
+        type: 'submap-link',
+        value: -0.43,
+        width: 100,
+        x1: 200,
+        x2: 300,
+        y1: 750,
+        y2: 700,
+      },
+    ];
+    const result = groupOverlayEntities(overlayBioEntities);
+
+    expect(result['0'][0].amount).toBe(2);
+    expect(result['1'][0].amount).toBe(1);
+    expect(result['2']).toBeUndefined();
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/groupOverlayEntities.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/groupOverlayEntities.ts
new file mode 100644
index 00000000..984574cf
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/groupOverlayEntities.ts
@@ -0,0 +1,34 @@
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import findMatchingSubmapLinkRectangle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/findMatchingSubmapLinkRectangle';
+import { OverlayBioEntityGroupedElementsType } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
+
+export default function groupOverlayEntities(
+  overlayBioEntities: Array<OverlayBioEntityRender>,
+): OverlayBioEntityGroupedElementsType {
+  const groupedElements: OverlayBioEntityGroupedElementsType = {};
+
+  overlayBioEntities.forEach(overlayBioEntity => {
+    if (overlayBioEntity.type !== 'submap-link') {
+      return;
+    }
+    if (!groupedElements[overlayBioEntity.overlayId]) {
+      groupedElements[overlayBioEntity.overlayId] = [];
+    }
+
+    const matchedElement = findMatchingSubmapLinkRectangle(
+      groupedElements[overlayBioEntity.overlayId],
+      overlayBioEntity,
+    );
+
+    if (!matchedElement) {
+      groupedElements[overlayBioEntity.overlayId].push({
+        ...overlayBioEntity,
+        amount: 1,
+      });
+    } else {
+      matchedElement.amount += 1;
+    }
+  });
+
+  return groupedElements;
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/processOverlayGroupedElements.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/processOverlayGroupedElements.test.ts
new file mode 100644
index 00000000..680002ea
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/processOverlayGroupedElements.test.ts
@@ -0,0 +1,55 @@
+/* eslint-disable no-magic-numbers */
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
+import processOverlayGroupedElements from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/processOverlayGroupedElements';
+import { OverlayBioEntityGroupedElementsType } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
+
+describe('processOverlayGroupedElements', () => {
+  it('should correctly process overlay grouped elements and add to entityOverlays', () => {
+    const groupedElements = {
+      '1': [
+        {
+          amount: 1,
+          color: null,
+          height: 50,
+          hexColor: '#0000001a',
+          id: '5',
+          modelId: 0,
+          overlayId: 1,
+          type: 'submap-link',
+          value: 0.1,
+          width: 100,
+          x1: 1200,
+          x2: 1300,
+          y1: 550,
+          y2: 500,
+        },
+        {
+          amount: 1,
+          color: null,
+          height: 50,
+          hexColor: '#0000001a',
+          id: '5',
+          modelId: 0,
+          overlayId: 1,
+          type: 'submap-link',
+          value: -0.43,
+          width: 100,
+          x1: 200,
+          x2: 300,
+          y1: 750,
+          y2: 700,
+        },
+      ],
+    } as OverlayBioEntityGroupedElementsType;
+
+    const entityOverlays: Array<OverlayBioEntityRender> = [];
+    const getColor: GetOverlayBioEntityColorByAvailableProperties = jest.fn(() => 'color');
+
+    processOverlayGroupedElements(groupedElements, entityOverlays, getColor);
+
+    expect(entityOverlays.length).toBe(2);
+    expect(entityOverlays[0].height).toEqual(25);
+    expect(entityOverlays[1].height).toEqual(25);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/processOverlayGroupedElements.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/processOverlayGroupedElements.ts
new file mode 100644
index 00000000..7e2a9b43
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/processOverlayGroupedElements.ts
@@ -0,0 +1,35 @@
+/* eslint-disable no-magic-numbers */
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
+import sortElementOverlayByColor from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/sortElementOverlayByColor';
+import calculateOverlayDimensions from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/calculateOverlayDimensions';
+import { OverlayBioEntityGroupedElementsType } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
+
+export default function processOverlayGroupedElements(
+  groupedElements: OverlayBioEntityGroupedElementsType,
+  entityOverlays: Array<OverlayBioEntityRender>,
+  getColor: GetOverlayBioEntityColorByAvailableProperties,
+): void {
+  Object.values(groupedElements).forEach(elementOverlay => {
+    const overlaysPerGroup: Array<OverlayBioEntityRender> = [];
+    sortElementOverlayByColor(elementOverlay, getColor);
+
+    const totalHeight = elementOverlay[0].height;
+    const totalAmount = elementOverlay.reduce(
+      (accumulator: number, overlay) => accumulator + overlay.amount,
+      0,
+    );
+
+    elementOverlay.forEach((overlay, index) => {
+      const overlayEntity = calculateOverlayDimensions(
+        overlay,
+        index,
+        totalAmount,
+        totalHeight,
+        overlaysPerGroup,
+      );
+      overlaysPerGroup.push(overlayEntity);
+    });
+    entityOverlays.push(...overlaysPerGroup);
+  });
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/sortElementOverlayByColor.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/sortElementOverlayByColor.test.ts
new file mode 100644
index 00000000..1f39cc2a
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/sortElementOverlayByColor.test.ts
@@ -0,0 +1,51 @@
+/* eslint-disable no-magic-numbers */
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
+import sortElementOverlayByColor from './sortElementOverlayByColor';
+
+describe('sortElementOverlayByColor', () => {
+  it('should sort elements by color', () => {
+    const elementOverlay: Array<OverlayBioEntityRender & { amount: number }> = [
+      {
+        amount: 1,
+        color: null,
+        height: 50,
+        hexColor: '#0000001a',
+        id: '5',
+        modelId: 0,
+        overlayId: 0,
+        type: 'submap-link',
+        value: 0.1,
+        width: 100,
+        x1: 1200,
+        x2: 1300,
+        y1: 550,
+        y2: 500,
+      },
+      {
+        amount: 1,
+        color: null,
+        height: 50,
+        hexColor: '#0000001a',
+        id: '2',
+        modelId: 0,
+        overlayId: 1,
+        type: 'submap-link',
+        value: -0.43,
+        width: 100,
+        x1: 200,
+        x2: 300,
+        y1: 750,
+        y2: 700,
+      },
+    ];
+
+    const getColor: GetOverlayBioEntityColorByAvailableProperties = jest.fn(
+      entity => `#A633C${entity.id}`,
+    );
+    sortElementOverlayByColor(elementOverlay, getColor);
+
+    expect(elementOverlay[0].id).toEqual('2');
+    expect(elementOverlay[1].id).toEqual('5');
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/sortElementOverlayByColor.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/sortElementOverlayByColor.ts
new file mode 100644
index 00000000..b185b637
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/sortElementOverlayByColor.ts
@@ -0,0 +1,20 @@
+/* eslint-disable no-magic-numbers */
+import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+
+export default function sortElementOverlayByColor(
+  elementOverlay: Array<OverlayBioEntityRender & { amount: number }>,
+  getColor: GetOverlayBioEntityColorByAvailableProperties,
+): void {
+  elementOverlay.sort((a, b) => {
+    const colorA = getColor(a);
+    const colorB = getColor(b);
+    if (colorA === colorB) {
+      return 0;
+    }
+    if (colorA < colorB) {
+      return -1;
+    }
+    return 1;
+  });
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.ts
index 6c98bf80..96083434 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle.ts
@@ -19,8 +19,8 @@ export default function getStyle({
   zIndex = 1,
 }: {
   geometry?: Geometry;
-  borderColor?: Color;
-  fillColor?: Color;
+  borderColor?: Color | string;
+  fillColor?: Color | string;
   lineWidth?: number;
   lineDash?: Array<number>;
   zIndex?: number;
@@ -28,11 +28,11 @@ export default function getStyle({
   return new Style({
     geometry,
     stroke: getStroke({
-      color: rgbToHex(borderColor),
+      color: typeof borderColor === 'string' ? borderColor : rgbToHex(borderColor),
       width: lineWidth,
       lineDash,
     }),
-    fill: getFill({ color: rgbToHex(fillColor) }),
+    fill: getFill({ color: typeof fillColor === 'string' ? fillColor : rgbToHex(fillColor) }),
     zIndex,
   });
 }
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts
index e11025d8..a6ca120f 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts
@@ -5,7 +5,10 @@ import { Fill, Stroke, Style } from 'ol/style';
 import { createFeatureFromExtent } from './createFeatureFromExtent';
 
 const getBioEntityOverlayFeatureStyle = (color: string): Style =>
-  new Style({ fill: new Fill({ color }), stroke: new Stroke({ color: 'black', width: 1 }) });
+  new Style({
+    fill: new Fill({ color }),
+    stroke: new Stroke({ color: 'black', width: 1 }),
+  });
 
 export const createOverlayGeometryFeature = (
   [xMin, yMin, xMax, yMax]: number[],
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor.ts
index 46d71907..0ac575a4 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor.ts
@@ -14,7 +14,9 @@ import { getHexStringColorFromRGBIntWithAlpha } from '@/utils/convert/getHexStri
 import { getHexTricolorGradientColorWithAlpha } from '@/utils/convert/getHexTricolorGradientColorWithAlpha';
 import { useCallback, useMemo } from 'react';
 
-type GetOverlayBioEntityColorByAvailableProperties = (entity: OverlayBioEntityRender) => string;
+export type GetOverlayBioEntityColorByAvailableProperties = (
+  entity: OverlayBioEntityRender,
+) => string;
 
 type UseTriColorLerpReturn = {
   getOverlayBioEntityColorByAvailableProperties: GetOverlayBioEntityColorByAvailableProperties;
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.test.ts
index ded469b2..852f8093 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.test.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.test.ts
@@ -88,11 +88,4 @@ describe('useOlMapCommonLayers - util', () => {
     expect(result[2]).toBeInstanceOf(VectorLayer);
     expect(result[2].getSourceState()).toBe('ready');
   });
-
-  it('should return valid VectorLayer instance [4]', () => {
-    const result = getRenderedHookResults();
-
-    expect(result[3]).toBeInstanceOf(VectorLayer);
-    expect(result[3].getSourceState()).toBe('ready');
-  });
 });
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.ts b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.ts
index bca964e7..bfec74c6 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.ts
@@ -1,15 +1,13 @@
 /* eslint-disable no-magic-numbers */
-import { useOlMapOverlaysLayer } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useOlMapOverlaysLayer';
 import { useOlMapPinsLayer } from '@/components/Map/MapViewer/utils/config/pinsLayer/useOlMapPinsLayer';
 import { useOlMapReactionsLayer } from '@/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer';
 import { useOlMapCommentsLayer } from '@/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer';
 import { MapConfig } from '../../MapViewer.types';
 
 export const useOlMapCommonLayers = (): MapConfig['layers'] => {
-  const overlaysLayer = useOlMapOverlaysLayer();
   const pinsLayer = useOlMapPinsLayer();
   const reactionsLayer = useOlMapReactionsLayer();
   const commentsLayer = useOlMapCommentsLayer();
 
-  return [overlaysLayer, pinsLayer, reactionsLayer, commentsLayer];
+  return [pinsLayer, reactionsLayer, commentsLayer];
 };
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts
index c0be0af8..ffcd6326 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts
@@ -5,6 +5,7 @@ import { renderHook } from '@testing-library/react';
 import BaseLayer from 'ol/layer/Base';
 import TileLayer from 'ol/layer/Tile';
 import React from 'react';
+import VectorLayer from 'ol/layer/Vector';
 import { useOlMapLayers } from './useOlMapLayers';
 
 const useRefValue = {
@@ -74,4 +75,11 @@ describe('useOlMapLayers - util', () => {
     expect(result[0]).toBeInstanceOf(TileLayer);
     expect(result[0].getSourceState()).toBe('ready');
   });
+
+  it('should return valid VectorLayer instance [2]', () => {
+    const result = getRenderedHookResults();
+
+    expect(result[1]).toBeInstanceOf(VectorLayer);
+    expect(result[1].getSourceState()).toBe('ready');
+  });
 });
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts b/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts
index f05f8c9d..10169c98 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts
@@ -1,9 +1,11 @@
 /* eslint-disable no-magic-numbers */
+import { useOlMapOverlaysLayer } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useOlMapOverlaysLayer';
 import { MapConfig } from '../../MapViewer.types';
 import { useOlMapTileLayer } from './useOlMapTileLayer';
 
 export const useOlMapLayers = (): MapConfig['layers'] => {
+  const overlaysLayer = useOlMapOverlaysLayer();
   const tileLayer = useOlMapTileLayer();
 
-  return [tileLayer];
+  return [tileLayer, overlaysLayer];
 };
-- 
GitLab


From c47e1c1da8c237894f5b7e456b1dd6f355923e05 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 14 Nov 2024 08:50:42 +0100
Subject: [PATCH 05/29] feat(vector-map): implement line overlay functionality

---
 .../reactionsLayer/useOlMapReactionsLayer.ts  | 35 +++++++--
 .../utils/shapes/overlay/LineOverlay.test.ts  | 60 +++++++++++++++
 .../utils/shapes/overlay/LineOverlay.ts       | 75 +++++++++++++++++++
 3 files changed, 164 insertions(+), 6 deletions(-)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/LineOverlay.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/LineOverlay.ts

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index 4e0b8bd0..325d3097 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -37,6 +37,7 @@ import { groupBy } from '@/utils/array/groupBy';
 import { useGetOverlayColor } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import getOverlays from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/getOverlays';
+import LineOverlay from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/LineOverlay';
 
 export const useOlMapReactionsLayer = ({
   mapInstance,
@@ -62,11 +63,33 @@ export const useOlMapReactionsLayer = ({
   }, [currentModelId, dispatch]);
 
   const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
-  const groupedOverlays = useMemo(() => {
-    const grouped = groupBy(bioEntities, bioEntity => bioEntity.id.toString());
+
+  const groupedElementsOverlays = useMemo(() => {
+    const elementsBioEntitesOverlay = bioEntities.filter(bioEntity => bioEntity.type !== 'line');
+    const grouped = groupBy(elementsBioEntitesOverlay, bioEntity => bioEntity.id.toString());
     return getOverlays(grouped, getOverlayBioEntityColorByAvailableProperties);
   }, [bioEntities, getOverlayBioEntityColorByAvailableProperties]);
 
+  const linesOverlays = useMemo(() => {
+    return bioEntities.filter(bioEntity => bioEntity.type === 'line');
+  }, [bioEntities]);
+
+  const linesOverlaysFeatures = useMemo(() => {
+    return linesOverlays.map(lineOverlay => {
+      return new LineOverlay({
+        lineOverlay,
+        getOverlayColor: getOverlayBioEntityColorByAvailableProperties,
+        pointToProjection,
+        mapInstance,
+      }).lineFeature;
+    });
+  }, [
+    getOverlayBioEntityColorByAvailableProperties,
+    linesOverlays,
+    mapInstance,
+    pointToProjection,
+  ]);
+
   const reactions = useMemo(() => {
     return modelReactions.map(reaction => {
       const reactionShapes = shapes && shapes[reaction.sboTerm];
@@ -182,7 +205,7 @@ export const useOlMapReactionsLayer = ({
             modifications: element.modificationResidues,
             lineTypes,
             bioShapes: shapes,
-            overlays: groupedOverlays[element.id],
+            overlays: groupedElementsOverlays[element.id],
             overlaysOrder,
             getOverlayColor: getOverlayBioEntityColorByAvailableProperties,
           }),
@@ -196,7 +219,7 @@ export const useOlMapReactionsLayer = ({
     pointToProjection,
     mapInstance,
     lineTypes,
-    groupedOverlays,
+    groupedElementsOverlays,
     overlaysOrder,
     getOverlayBioEntityColorByAvailableProperties,
   ]);
@@ -204,8 +227,8 @@ export const useOlMapReactionsLayer = ({
   const features = useMemo(() => {
     const reactionsFeatures = reactions.flat();
     const elementsFeatures = elements.map(element => element.feature);
-    return [...reactionsFeatures, ...elementsFeatures];
-  }, [elements, reactions]);
+    return [...reactionsFeatures, ...elementsFeatures, ...linesOverlaysFeatures];
+  }, [elements, linesOverlaysFeatures, reactions]);
 
   const vectorSource = useMemo(() => {
     return new VectorSource({
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/LineOverlay.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/LineOverlay.test.ts
new file mode 100644
index 00000000..4efbc9d6
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/LineOverlay.test.ts
@@ -0,0 +1,60 @@
+/* eslint-disable no-magic-numbers */
+import { Feature, Map } from 'ol';
+import { Stroke, Style } from 'ol/style';
+import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
+import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
+import View from 'ol/View';
+import LineOverlay, {
+  LineOverlayProps,
+} from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/LineOverlay';
+import { Coordinate } from 'ol/coordinate';
+
+jest.mock('../style/getStroke');
+jest.mock('../style/getFill');
+jest.mock('../style/rgbToHex');
+
+describe('LineOverlay', () => {
+  let props: LineOverlayProps;
+
+  beforeEach(() => {
+    const dummyElement = document.createElement('div');
+    const mapInstance = new Map({
+      target: dummyElement,
+      view: new View({
+        zoom: 5,
+        minZoom: 3,
+        maxZoom: 7,
+      }),
+    });
+    props = {
+      lineOverlay: {
+        color: null,
+        height: 2,
+        id: '3',
+        modelId: 1,
+        overlayId: 0,
+        type: 'line',
+        value: 0.43,
+        width: 2,
+        x1: 1,
+        x2: 3,
+        y1: 3,
+        y2: 1,
+      },
+      getOverlayColor: (): string => '#AABB11',
+      pointToProjection: ({ x, y }: { x: number; y: number }): Coordinate => [x, y],
+      mapInstance,
+    };
+
+    (getStroke as jest.Mock).mockReturnValue(new Stroke());
+    (getFill as jest.Mock).mockReturnValue(new Style());
+    (rgbToHex as jest.Mock).mockReturnValue('#FFFFFF');
+  });
+
+  it('should initialize line overlay feature', () => {
+    const lineOverlay = new LineOverlay(props);
+
+    expect(lineOverlay.lineFeature).toBeInstanceOf(Feature);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/LineOverlay.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/LineOverlay.ts
new file mode 100644
index 00000000..b6d8eecb
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/LineOverlay.ts
@@ -0,0 +1,75 @@
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
+import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
+import { LineString } from 'ol/geom';
+import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle';
+import { Feature } from 'ol';
+import { FeatureLike } from 'ol/Feature';
+import Style from 'ol/style/Style';
+import { MapInstance } from '@/types/map';
+
+export type LineOverlayProps = {
+  lineOverlay: OverlayBioEntityRender;
+  getOverlayColor: GetOverlayBioEntityColorByAvailableProperties;
+  pointToProjection: UsePointToProjectionResult;
+  mapInstance: MapInstance;
+};
+
+export default class LineOverlay {
+  lineOverlay: OverlayBioEntityRender;
+
+  getOverlayColor: GetOverlayBioEntityColorByAvailableProperties;
+
+  pointToProjection: UsePointToProjectionResult;
+
+  mapInstance: MapInstance;
+
+  lineFeature: Feature;
+
+  constructor({ lineOverlay, getOverlayColor, pointToProjection, mapInstance }: LineOverlayProps) {
+    this.lineOverlay = lineOverlay;
+    this.getOverlayColor = getOverlayColor;
+    this.pointToProjection = pointToProjection;
+    this.mapInstance = mapInstance;
+    this.lineFeature = this.drawOverlay();
+  }
+
+  drawOverlay(): Feature {
+    const points = [
+      this.pointToProjection({ x: this.lineOverlay.x1, y: this.lineOverlay.y1 }),
+      this.pointToProjection({ x: this.lineOverlay.x2, y: this.lineOverlay.y2 }),
+    ];
+    const color = this.getOverlayColor(this.lineOverlay);
+    const lineString = new LineString(points);
+    const lineStyle = getStyle({
+      geometry: lineString,
+      borderColor: color,
+      lineWidth: 6,
+      zIndex: 99999,
+    });
+    const lineFeature = new Feature<LineString>({
+      geometry: lineString,
+      style: lineStyle,
+      lineWidth: 6,
+    });
+    lineFeature.setStyle(this.getStyle.bind(this));
+    return lineFeature;
+  }
+
+  protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
+    const maxZoom = this.mapInstance?.getView().get('originalMaxZoom');
+    const minResolution = this.mapInstance?.getView().getResolutionForZoom(maxZoom);
+    const style = feature.get('style');
+    if (!minResolution || !style) {
+      return [];
+    }
+
+    const scale = minResolution / resolution;
+    const lineWidth = feature.get('lineWidth') * scale;
+
+    if (style instanceof Style && style.getStroke()) {
+      style.getStroke()?.setWidth(lineWidth);
+    }
+    return style;
+  }
+}
-- 
GitLab


From c797efb1435c289ff50899de26f1a014de6fd6ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 14 Nov 2024 09:29:35 +0100
Subject: [PATCH 06/29] feat(vector-map): implement marker overlay
 functionality

---
 .../reactionsLayer/useOlMapReactionsLayer.ts  | 29 ++++++-
 .../shapes/overlay/MarkerOverlay.test.ts      | 61 ++++++++++++++
 .../utils/shapes/overlay/MarkerOverlay.ts     | 83 +++++++++++++++++++
 .../overlaysLayer/useOverlayFeatures.ts       |  1 +
 4 files changed, 173 insertions(+), 1 deletion(-)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay.ts

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index 325d3097..0dca84c6 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -38,6 +38,9 @@ import { useGetOverlayColor } from '@/components/Map/MapViewer/utils/config/over
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import getOverlays from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/getOverlays';
 import LineOverlay from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/LineOverlay';
+import { markersSufraceOfCurrentMapDataSelector } from '@/redux/markers/markers.selectors';
+import { parseSurfaceMarkersToBioEntityRender } from '@/components/Map/MapViewer/utils/config/overlaysLayer/parseSurfaceMarkersToBioEntityRender';
+import MarkerOverlay from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay';
 
 export const useOlMapReactionsLayer = ({
   mapInstance,
@@ -53,6 +56,9 @@ export const useOlMapReactionsLayer = ({
   const lineTypes = useSelector(lineTypesSelector);
   const arrowTypes = useSelector(arrowTypesSelector);
   const overlaysOrder = useSelector(getOverlayOrderSelector);
+  const currentMarkers = useAppSelector(markersSufraceOfCurrentMapDataSelector);
+  const markersRender = parseSurfaceMarkersToBioEntityRender(currentMarkers);
+
   const { getOverlayBioEntityColorByAvailableProperties } = useGetOverlayColor();
 
   const pointToProjection = usePointToProjection();
@@ -90,6 +96,22 @@ export const useOlMapReactionsLayer = ({
     pointToProjection,
   ]);
 
+  const markerOverlaysFeatures = useMemo(() => {
+    return markersRender.map(marker => {
+      return new MarkerOverlay({
+        markerOverlay: marker,
+        getOverlayColor: getOverlayBioEntityColorByAvailableProperties,
+        pointToProjection,
+        mapInstance,
+      }).markerFeature;
+    });
+  }, [
+    getOverlayBioEntityColorByAvailableProperties,
+    mapInstance,
+    markersRender,
+    pointToProjection,
+  ]);
+
   const reactions = useMemo(() => {
     return modelReactions.map(reaction => {
       const reactionShapes = shapes && shapes[reaction.sboTerm];
@@ -227,7 +249,12 @@ export const useOlMapReactionsLayer = ({
   const features = useMemo(() => {
     const reactionsFeatures = reactions.flat();
     const elementsFeatures = elements.map(element => element.feature);
-    return [...reactionsFeatures, ...elementsFeatures, ...linesOverlaysFeatures];
+    return [
+      ...reactionsFeatures,
+      ...elementsFeatures,
+      ...linesOverlaysFeatures,
+      ...markerOverlaysFeatures,
+    ];
   }, [elements, linesOverlaysFeatures, reactions]);
 
   const vectorSource = useMemo(() => {
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay.test.ts
new file mode 100644
index 00000000..4fe3c4aa
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay.test.ts
@@ -0,0 +1,61 @@
+/* eslint-disable no-magic-numbers */
+import { Feature, Map } from 'ol';
+import { Stroke, Style } from 'ol/style';
+import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
+import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
+import View from 'ol/View';
+import { Coordinate } from 'ol/coordinate';
+import MarkerOverlay, {
+  MarkerOverlayProps,
+} from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay';
+
+jest.mock('../style/getStroke');
+jest.mock('../style/getFill');
+jest.mock('../style/rgbToHex');
+
+describe('MarkerOverlay', () => {
+  let props: MarkerOverlayProps;
+
+  beforeEach(() => {
+    const dummyElement = document.createElement('div');
+    const mapInstance = new Map({
+      target: dummyElement,
+      view: new View({
+        zoom: 5,
+        minZoom: 3,
+        maxZoom: 7,
+      }),
+    });
+    props = {
+      markerOverlay: {
+        color: null,
+        height: 2,
+        hexColor: '#A756BA90',
+        id: '3',
+        modelId: 1,
+        overlayId: 0,
+        type: 'rectangle',
+        value: 0.43,
+        width: 2,
+        x1: 1,
+        x2: 3,
+        y1: 3,
+        y2: 1,
+      },
+      getOverlayColor: (): string => '#AABB11',
+      pointToProjection: ({ x, y }: { x: number; y: number }): Coordinate => [x, y],
+      mapInstance,
+    };
+
+    (getStroke as jest.Mock).mockReturnValue(new Stroke());
+    (getFill as jest.Mock).mockReturnValue(new Style());
+    (rgbToHex as jest.Mock).mockReturnValue('#FFFFFF');
+  });
+
+  it('should initialize line overlay feature', () => {
+    const markerOverlay = new MarkerOverlay(props);
+
+    expect(markerOverlay.markerFeature).toBeInstanceOf(Feature);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay.ts
new file mode 100644
index 00000000..a0336335
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay.ts
@@ -0,0 +1,83 @@
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
+import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
+import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle';
+import { Feature } from 'ol';
+import { FeatureLike } from 'ol/Feature';
+import Style from 'ol/style/Style';
+import { MapInstance } from '@/types/map';
+import Polygon from 'ol/geom/Polygon';
+
+export type MarkerOverlayProps = {
+  markerOverlay: OverlayBioEntityRender;
+  getOverlayColor: GetOverlayBioEntityColorByAvailableProperties;
+  pointToProjection: UsePointToProjectionResult;
+  mapInstance: MapInstance;
+};
+
+export default class MarkerOverlay {
+  markerOverlay: OverlayBioEntityRender;
+
+  getOverlayColor: GetOverlayBioEntityColorByAvailableProperties;
+
+  pointToProjection: UsePointToProjectionResult;
+
+  mapInstance: MapInstance;
+
+  markerFeature: Feature;
+
+  constructor({
+    markerOverlay,
+    getOverlayColor,
+    pointToProjection,
+    mapInstance,
+  }: MarkerOverlayProps) {
+    this.markerOverlay = markerOverlay;
+    this.getOverlayColor = getOverlayColor;
+    this.pointToProjection = pointToProjection;
+    this.mapInstance = mapInstance;
+    this.markerFeature = this.drawOverlay();
+  }
+
+  drawOverlay(): Feature {
+    const color = this.getOverlayColor(this.markerOverlay);
+    const polygon = new Polygon([
+      [
+        this.pointToProjection({ x: this.markerOverlay.x1, y: this.markerOverlay.y1 }),
+        this.pointToProjection({ x: this.markerOverlay.x2, y: this.markerOverlay.y1 }),
+        this.pointToProjection({ x: this.markerOverlay.x2, y: this.markerOverlay.y2 }),
+        this.pointToProjection({ x: this.markerOverlay.x1, y: this.markerOverlay.y2 }),
+      ],
+    ]);
+    const style = getStyle({
+      geometry: polygon,
+      fillColor: this.markerOverlay.hexColor || color,
+      lineWidth: 1,
+      zIndex: 99999,
+    });
+    const markerFeature = new Feature<Polygon>({
+      geometry: polygon,
+      style,
+      lineWidth: 1,
+    });
+    markerFeature.setStyle(this.getStyle.bind(this));
+    return markerFeature;
+  }
+
+  protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
+    const maxZoom = this.mapInstance?.getView().get('originalMaxZoom');
+    const minResolution = this.mapInstance?.getView().getResolutionForZoom(maxZoom);
+    const style = feature.get('style');
+    if (!minResolution || !style) {
+      return [];
+    }
+
+    const scale = minResolution / resolution;
+    const lineWidth = feature.get('lineWidth') * scale;
+
+    if (style instanceof Style && style.getStroke()) {
+      style.getStroke()?.setWidth(lineWidth);
+    }
+    return style;
+  }
+}
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts
index 107add31..020e6ae9 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts
@@ -22,6 +22,7 @@ export const useOverlayFeatures = (): Feature<Polygon>[] | Feature<SimpleGeometr
   const overlaysOrder = useAppSelector(getOverlayOrderSelector);
   const currentMarkers = useAppSelector(markersSufraceOfCurrentMapDataSelector);
   const markersRender = parseSurfaceMarkersToBioEntityRender(currentMarkers);
+
   const bioEntities = useBioEntitiesWithSubmapsLinks();
 
   const markersFeatures = useMemo(
-- 
GitLab


From 5287f115f2878724db9abe28d4057c5af77ed7ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 14 Nov 2024 10:11:19 +0100
Subject: [PATCH 07/29] refactor(vector-map): rendering modifications after
 element rendering

---
 .../utils/shapes/elements/MapElement.ts       | 24 +++++++++----------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
index 94d5f96f..f485c3fb 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
@@ -156,18 +156,6 @@ export default class MapElement extends BaseMultiPolygon {
   }
 
   protected createPolygons(): void {
-    this.modifications.forEach(modification => {
-      if (modification.state === null) {
-        return;
-      }
-
-      const shapes = this.bioShapes[modification.sboTerm];
-      if (!shapes) {
-        return;
-      }
-      this.drawModification(modification, shapes);
-    });
-
     if (this.lineType) {
       this.lineDash = this.lineTypes[this.lineType] || [];
     }
@@ -181,6 +169,18 @@ export default class MapElement extends BaseMultiPolygon {
       this.drawElementPolygon(homodimerShift, homodimerOffset);
     }
     this.drawOverlays();
+
+    this.modifications.forEach(modification => {
+      if (modification.state === null) {
+        return;
+      }
+
+      const shapes = this.bioShapes[modification.sboTerm];
+      if (!shapes) {
+        return;
+      }
+      this.drawModification(modification, shapes);
+    });
   }
 
   drawModification(modification: Modification, shapes: Array<Shape>): void {
-- 
GitLab


From 708f80ef4c566071a65daba4f63c030a90c1aed5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 14 Nov 2024 11:55:03 +0100
Subject: [PATCH 08/29] feat(vector-map): hide modifications when text is
 hidden

---
 .../utils/shapes/elements/BaseMultiPolygon.ts | 47 +++++++++++--------
 .../utils/shapes/elements/Compartment.ts      |  7 ++-
 .../shapes/elements/CompartmentPathway.ts     |  4 +-
 .../utils/shapes/elements/MapElement.ts       | 17 +++++--
 4 files changed, 47 insertions(+), 28 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
index 84dfd159..f039c015 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
@@ -77,10 +77,6 @@ export default abstract class BaseMultiPolygon {
 
   styles: Array<Style> = [];
 
-  polygonsTexts: Array<string> = [];
-
-  lineWidths: Array<number> = [];
-
   feature: Feature = new Feature();
 
   pointToProjection: UsePointToProjectionResult;
@@ -142,6 +138,9 @@ export default abstract class BaseMultiPolygon {
         pointToProjection: this.pointToProjection,
       });
       const textPolygon = new Polygon([[textCoords, textCoords]]);
+      textPolygon.set('type', 'text');
+      textPolygon.set('text', this.text);
+      textPolygon.set('fontSize', this.fontSize);
       const textStyle = getTextStyle({
         text: this.text,
         fontSize: this.fontSize,
@@ -151,7 +150,6 @@ export default abstract class BaseMultiPolygon {
       });
       textStyle.setGeometry(textPolygon);
       this.styles.push(textStyle);
-      this.polygonsTexts.push(this.text);
       this.polygons.push(textPolygon);
     }
   }
@@ -177,29 +175,38 @@ export default abstract class BaseMultiPolygon {
   }
 
   protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
+    const styles: Array<Style> = [];
     const getTextScale = feature.get('getTextScale');
     let textScale = 1;
     if (getTextScale instanceof Function) {
       textScale = getTextScale(resolution);
     }
-    let textIndex = 0;
-    let strokeIndex = 0;
+
+    let type: string;
+    let fontSize: number;
+    let lineWidth: number;
+    let text: string;
+
     this.styles.forEach(style => {
-      if (style.getText()) {
-        if (this.fontSize * textScale > 4) {
-          style.getText()?.setScale(textScale);
-          style.getText()?.setText(this.polygonsTexts[textIndex]);
-          textIndex += 1;
-        } else {
-          style.getText()?.setText(undefined);
-        }
+      if (style.getGeometry() instanceof Polygon) {
+        type = (style.getGeometry() as Polygon).get('type');
+        text = (style.getGeometry() as Polygon).get('text');
+        fontSize = (style.getGeometry() as Polygon).get('fontSize');
+        lineWidth = (style.getGeometry() as Polygon).get('lineWidth');
+      }
+      if (['modification', 'text'].includes(type) && textScale * fontSize <= 4) {
+        return;
+      }
+
+      if (type === 'text') {
+        style.getText()?.setScale(textScale);
+        style.getText()?.setText(text);
       }
-      if (style.getStroke()) {
-        const lineWidth = this.lineWidths[strokeIndex] * textScale;
-        style.getStroke()?.setWidth(lineWidth);
-        strokeIndex += 1;
+      if (style.getStroke() && lineWidth) {
+        style.getStroke()?.setWidth(lineWidth * textScale);
       }
+      styles.push(style);
     });
-    return this.styles;
+    return styles;
   }
 }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
index b1aca3d8..9adc9ece 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
@@ -108,6 +108,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
 
   protected createPolygons(): void {
     const framePolygon = new Polygon([this.outerCoords, this.innerCoords]);
+    framePolygon.set('type', 'compartment');
     this.styles.push(
       new Style({
         geometry: framePolygon,
@@ -118,6 +119,8 @@ export default abstract class Compartment extends BaseMultiPolygon {
     this.polygons.push(framePolygon);
 
     const outerPolygon = new Polygon([this.outerCoords]);
+    outerPolygon.set('type', 'compartment');
+    outerPolygon.set('lineWidth', this.outerWidth);
     this.styles.push(
       new Style({
         geometry: outerPolygon,
@@ -125,10 +128,11 @@ export default abstract class Compartment extends BaseMultiPolygon {
         zIndex: this.zIndex,
       }),
     );
-    this.lineWidths.push(this.outerWidth);
     this.polygons.push(outerPolygon);
 
     const innerPolygon = new Polygon([this.innerCoords]);
+    innerPolygon.set('type', 'compartment');
+    innerPolygon.set('lineWidth', this.innerWidth);
     this.styles.push(
       new Style({
         geometry: innerPolygon,
@@ -137,7 +141,6 @@ export default abstract class Compartment extends BaseMultiPolygon {
         zIndex: this.zIndex,
       }),
     );
-    this.lineWidths.push(this.innerWidth);
     this.polygons.push(innerPolygon);
   }
 }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
index 8adec3ec..cc1e287c 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
@@ -98,7 +98,9 @@ export default class CompartmentPathway extends BaseMultiPolygon {
         this.pointToProjection({ x: this.x, y: this.y + this.height }),
       ],
     ]);
-    this.lineWidths.push(this.outerWidth);
+    compartmentPolygon.set('type', 'compartment');
+    compartmentPolygon.set('lineWidth', this.outerWidth);
+
     this.styles.push(
       getStyle({
         geometry: compartmentPolygon,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
index f485c3fb..c8934cc2 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
@@ -194,13 +194,15 @@ export default class MapElement extends BaseMultiPolygon {
         pointToProjection: this.pointToProjection,
         mirror: modification.direction && modification.direction === 'RIGHT',
       });
+      modificationPolygon.set('type', 'modification');
+      modificationPolygon.set('fontSize', modification.fontSize);
+      modificationPolygon.set('lineWidth', 1);
       const modificationStyle = new Style({
         geometry: modificationPolygon,
         stroke: getStroke({ color: rgbToHex(modification.borderColor) }),
         fill: getFill({ color: rgbToHex(modification.fillColor) }),
         zIndex: modification.z,
       });
-      this.lineWidths.push(1);
       this.polygons.push(modificationPolygon);
       this.styles.push(modificationStyle);
     });
@@ -222,6 +224,9 @@ export default class MapElement extends BaseMultiPolygon {
       const modificationTextPolygon = new Polygon([
         [modificationTextCoords, modificationTextCoords],
       ]);
+      modificationTextPolygon.set('type', 'text');
+      modificationTextPolygon.set('text', modificationText);
+      modificationTextPolygon.set('fontSize', modification.fontSize);
       const modificationTextStyle = getTextStyle({
         text: modificationText,
         fontSize: modification.fontSize,
@@ -231,7 +236,6 @@ export default class MapElement extends BaseMultiPolygon {
       });
       modificationTextStyle.setGeometry(modificationTextPolygon);
       this.styles.push(modificationTextStyle);
-      this.polygonsTexts.push(modificationText);
       this.polygons.push(modificationTextPolygon);
     }
   }
@@ -246,6 +250,8 @@ export default class MapElement extends BaseMultiPolygon {
         height: this.height - homodimerOffset + 10,
         pointToProjection: this.pointToProjection,
       });
+      activityBorderPolygon.set('type', 'activityBorder');
+      activityBorderPolygon.set('lineWidth', 1);
       const activityBorderStyle = getStyle({
         geometry: activityBorderPolygon,
         fillColor: { rgb: 0, alpha: 0 },
@@ -253,7 +259,6 @@ export default class MapElement extends BaseMultiPolygon {
         zIndex: this.zIndex,
       });
       this.polygons.push(activityBorderPolygon);
-      this.lineWidths.push(1);
       this.styles.push(activityBorderStyle);
     });
   }
@@ -268,6 +273,8 @@ export default class MapElement extends BaseMultiPolygon {
         height: this.height - homodimerOffset,
         pointToProjection: this.pointToProjection,
       });
+      elementPolygon.set('type', 'element');
+      elementPolygon.set('lineWidth', this.lineWidth);
       const elementStyle = getStyle({
         geometry: elementPolygon,
         borderColor: this.borderColor,
@@ -277,7 +284,6 @@ export default class MapElement extends BaseMultiPolygon {
         zIndex: this.zIndex,
       });
       this.polygons.push(elementPolygon);
-      this.lineWidths.push(this.lineWidth);
       this.styles.push(elementStyle);
     });
   }
@@ -303,6 +309,8 @@ export default class MapElement extends BaseMultiPolygon {
           this.pointToProjection({ x: xMin, y: entity.y2 }),
         ],
       ]);
+      polygon.set('type', 'overlay');
+      polygon.set('lineWidth', 1);
       const style = getStyle({
         geometry: polygon,
         borderColor: color,
@@ -310,7 +318,6 @@ export default class MapElement extends BaseMultiPolygon {
         zIndex: this.zIndex,
       });
       this.polygons.push(polygon);
-      this.lineWidths.push(1);
       this.styles.push(style);
     });
   }
-- 
GitLab


From 52b15aaf85238f2f6adb36cadeb99177d0462fda Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 14 Nov 2024 12:47:02 +0100
Subject: [PATCH 09/29] feat(vector-map): hide operator dots when text is
 hidden and hide arrows when it is unreadable

---
 .../MapViewerVector.constants.ts              |  7 +++++
 .../utils/shapes/reaction/Reaction.ts         | 31 ++++++++++++++++---
 2 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
index 9a036ac1..8e546623 100644
--- a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
@@ -12,6 +12,13 @@ export const BLACK_COLOR: Color = {
   rgb: -16777216,
 };
 
+export const REACTION_ELEMENT_TYPES = {
+  OPERATOR: 'operator',
+  SQUARE: 'square',
+  LINE: 'line',
+  ARROW: 'arrow',
+};
+
 export const COMPARTMENT_SQUARE_POINTS: Array<ShapeRelAbs | ShapeRelAbsBezierPoint> = [
   {
     type: 'REL_ABS_POINT',
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts
index 9bd713ce..c4fc7499 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts
@@ -9,7 +9,10 @@ import getArrowFeature from '@/components/Map/MapViewer/MapViewerVector/utils/sh
 import getShapePolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getShapePolygon';
 import Polygon from 'ol/geom/Polygon';
 import Style from 'ol/style/Style';
-import { WHITE_COLOR } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
+import {
+  REACTION_ELEMENT_TYPES,
+  WHITE_COLOR,
+} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import { FeatureLike } from 'ol/Feature';
 import { MapInstance } from '@/types/map';
 import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle';
@@ -157,6 +160,7 @@ export default class Reaction {
         pointToProjection: this.pointToProjection,
       });
       if (startArrowFeature) {
+        startArrowFeature.set('elementType', REACTION_ELEMENT_TYPES.ARROW);
         startArrowFeature.set('lineWidth', line.width);
         startArrowFeature.setStyle(this.getStyle.bind(this));
         arrowsFeatures.push(startArrowFeature);
@@ -184,6 +188,7 @@ export default class Reaction {
         pointToProjection: this.pointToProjection,
       });
       if (endArrowFeature) {
+        endArrowFeature.set('elementType', REACTION_ELEMENT_TYPES.ARROW);
         endArrowFeature.set('lineWidth', line.width);
         endArrowFeature.setStyle(this.getStyle.bind(this));
         arrowsFeatures.push(endArrowFeature);
@@ -206,6 +211,7 @@ export default class Reaction {
       lineWidth: line.width,
       id: this.id,
       type: FEATURE_TYPE.REACTION,
+      elementType: REACTION_ELEMENT_TYPES.LINE,
     });
     lineFeature.setStyle(this.getStyle.bind(this));
 
@@ -254,6 +260,7 @@ export default class Reaction {
       lineWidth: this.line.width,
       id: this.id,
       type: FEATURE_TYPE.REACTION,
+      elementType: REACTION_ELEMENT_TYPES.SQUARE,
     });
     squareFeature.setStyle(this.getStyle.bind(this));
     return squareFeature;
@@ -308,12 +315,15 @@ export default class Reaction {
       lineWidth: 1,
       id: this.id,
       type: FEATURE_TYPE.REACTION,
+      elementType: REACTION_ELEMENT_TYPES.OPERATOR,
+      fontSize: 10,
     });
     circleFeature.setStyle(this.getStyle.bind(this));
     return circleFeature;
   }
 
   protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
+    const styles: Array<Style> = [];
     const maxZoom = this.mapInstance?.getView().get('originalMaxZoom');
     const minResolution = this.mapInstance?.getView().getResolutionForZoom(maxZoom);
     const style = feature.get('style');
@@ -323,16 +333,29 @@ export default class Reaction {
 
     const scale = minResolution / resolution;
     const lineWidth = feature.get('lineWidth') * scale;
+    const type = feature.get('elementType');
+    const fontSize = feature.get('fontSize');
+
+    if (type === REACTION_ELEMENT_TYPES.OPERATOR && fontSize * scale <= 4) {
+      return [];
+    }
+    if (type === REACTION_ELEMENT_TYPES.ARROW && scale <= 0.08) {
+      return [];
+    }
 
-    if (style instanceof Style && style.getStroke()) {
+    if (style instanceof Style) {
       style.getStroke()?.setWidth(lineWidth);
+      style.getText()?.setScale(scale);
+      styles.push(style);
     } else if (Array.isArray(style)) {
       style.forEach(singleStyle => {
-        if (singleStyle instanceof Style && singleStyle.getStroke()) {
+        if (singleStyle instanceof Style) {
           singleStyle.getStroke()?.setWidth(lineWidth);
+          singleStyle.getText()?.setScale(scale);
+          styles.push(singleStyle);
         }
       });
     }
-    return style;
+    return styles;
   }
 }
-- 
GitLab


From 10c49a948a2ea4663f1a06a621bc3ed7605840ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 14 Nov 2024 12:53:38 +0100
Subject: [PATCH 10/29] feat(vector-map): add types const for map elements

---
 .../MapViewerVector/MapViewerVector.constants.ts      |  9 +++++++++
 .../utils/shapes/elements/BaseMultiPolygon.ts         |  8 ++++++--
 .../utils/shapes/elements/Compartment.ts              |  7 ++++---
 .../utils/shapes/elements/CompartmentPathway.ts       |  3 ++-
 .../utils/shapes/elements/MapElement.ts               | 11 ++++++-----
 5 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
index 8e546623..ac478d7b 100644
--- a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
@@ -19,6 +19,15 @@ export const REACTION_ELEMENT_TYPES = {
   ARROW: 'arrow',
 };
 
+export const MAP_ELEMENT_TYPES = {
+  TEXT: 'text',
+  MODIFICATION: 'modification',
+  ACTIVITY_BORDER: 'activityBorder',
+  ENTITY: 'entity',
+  OVERLAY: 'overlay',
+  COMPARTMENT: 'compartment',
+};
+
 export const COMPARTMENT_SQUARE_POINTS: Array<ShapeRelAbs | ShapeRelAbsBezierPoint> = [
   {
     type: 'REL_ABS_POINT',
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
index f039c015..cec6f17f 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
@@ -13,6 +13,7 @@ import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shape
 import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 import { Color } from '@/types/models';
+import { MAP_ELEMENT_TYPES } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 
 export interface BaseMapElementProps {
   type: string;
@@ -138,7 +139,7 @@ export default abstract class BaseMultiPolygon {
         pointToProjection: this.pointToProjection,
       });
       const textPolygon = new Polygon([[textCoords, textCoords]]);
-      textPolygon.set('type', 'text');
+      textPolygon.set('type', MAP_ELEMENT_TYPES.TEXT);
       textPolygon.set('text', this.text);
       textPolygon.set('fontSize', this.fontSize);
       const textStyle = getTextStyle({
@@ -194,7 +195,10 @@ export default abstract class BaseMultiPolygon {
         fontSize = (style.getGeometry() as Polygon).get('fontSize');
         lineWidth = (style.getGeometry() as Polygon).get('lineWidth');
       }
-      if (['modification', 'text'].includes(type) && textScale * fontSize <= 4) {
+      if (
+        [MAP_ELEMENT_TYPES.MODIFICATION, MAP_ELEMENT_TYPES.TEXT].includes(type) &&
+        textScale * fontSize <= 4
+      ) {
         return;
       }
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
index 9adc9ece..9c50b1bc 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
@@ -13,6 +13,7 @@ import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shape
 import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
 import { MapInstance } from '@/types/map';
 import { Color } from '@/types/models';
+import { MAP_ELEMENT_TYPES } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 
 export interface CompartmentProps {
   id: number;
@@ -108,7 +109,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
 
   protected createPolygons(): void {
     const framePolygon = new Polygon([this.outerCoords, this.innerCoords]);
-    framePolygon.set('type', 'compartment');
+    framePolygon.set('type', MAP_ELEMENT_TYPES.COMPARTMENT);
     this.styles.push(
       new Style({
         geometry: framePolygon,
@@ -119,7 +120,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
     this.polygons.push(framePolygon);
 
     const outerPolygon = new Polygon([this.outerCoords]);
-    outerPolygon.set('type', 'compartment');
+    outerPolygon.set('type', MAP_ELEMENT_TYPES.COMPARTMENT);
     outerPolygon.set('lineWidth', this.outerWidth);
     this.styles.push(
       new Style({
@@ -131,7 +132,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
     this.polygons.push(outerPolygon);
 
     const innerPolygon = new Polygon([this.innerCoords]);
-    innerPolygon.set('type', 'compartment');
+    innerPolygon.set('type', MAP_ELEMENT_TYPES.COMPARTMENT);
     innerPolygon.set('lineWidth', this.innerWidth);
     this.styles.push(
       new Style({
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
index cc1e287c..bcc70aa1 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
@@ -7,6 +7,7 @@ import {
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
 import {
   BLACK_COLOR,
+  MAP_ELEMENT_TYPES,
   WHITE_COLOR,
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import Polygon from 'ol/geom/Polygon';
@@ -98,7 +99,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
         this.pointToProjection({ x: this.x, y: this.y + this.height }),
       ],
     ]);
-    compartmentPolygon.set('type', 'compartment');
+    compartmentPolygon.set('type', MAP_ELEMENT_TYPES.COMPARTMENT);
     compartmentPolygon.set('lineWidth', this.outerWidth);
 
     this.styles.push(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
index c8934cc2..909c7c6b 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
@@ -13,6 +13,7 @@ import {
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
 import {
   BLACK_COLOR,
+  MAP_ELEMENT_TYPES,
   WHITE_COLOR,
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import BaseMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon';
@@ -194,7 +195,7 @@ export default class MapElement extends BaseMultiPolygon {
         pointToProjection: this.pointToProjection,
         mirror: modification.direction && modification.direction === 'RIGHT',
       });
-      modificationPolygon.set('type', 'modification');
+      modificationPolygon.set('type', MAP_ELEMENT_TYPES.MODIFICATION);
       modificationPolygon.set('fontSize', modification.fontSize);
       modificationPolygon.set('lineWidth', 1);
       const modificationStyle = new Style({
@@ -224,7 +225,7 @@ export default class MapElement extends BaseMultiPolygon {
       const modificationTextPolygon = new Polygon([
         [modificationTextCoords, modificationTextCoords],
       ]);
-      modificationTextPolygon.set('type', 'text');
+      modificationTextPolygon.set('type', MAP_ELEMENT_TYPES.TEXT);
       modificationTextPolygon.set('text', modificationText);
       modificationTextPolygon.set('fontSize', modification.fontSize);
       const modificationTextStyle = getTextStyle({
@@ -250,7 +251,7 @@ export default class MapElement extends BaseMultiPolygon {
         height: this.height - homodimerOffset + 10,
         pointToProjection: this.pointToProjection,
       });
-      activityBorderPolygon.set('type', 'activityBorder');
+      activityBorderPolygon.set('type', MAP_ELEMENT_TYPES.ACTIVITY_BORDER);
       activityBorderPolygon.set('lineWidth', 1);
       const activityBorderStyle = getStyle({
         geometry: activityBorderPolygon,
@@ -273,7 +274,7 @@ export default class MapElement extends BaseMultiPolygon {
         height: this.height - homodimerOffset,
         pointToProjection: this.pointToProjection,
       });
-      elementPolygon.set('type', 'element');
+      elementPolygon.set('type', MAP_ELEMENT_TYPES.ENTITY);
       elementPolygon.set('lineWidth', this.lineWidth);
       const elementStyle = getStyle({
         geometry: elementPolygon,
@@ -309,7 +310,7 @@ export default class MapElement extends BaseMultiPolygon {
           this.pointToProjection({ x: xMin, y: entity.y2 }),
         ],
       ]);
-      polygon.set('type', 'overlay');
+      polygon.set('type', MAP_ELEMENT_TYPES.OVERLAY);
       polygon.set('lineWidth', 1);
       const style = getStyle({
         geometry: polygon,
-- 
GitLab


From ff64dbaa10c8466b6fae661f45ea7e4e7461ebed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 14 Nov 2024 13:05:06 +0100
Subject: [PATCH 11/29] feat(vector-map): hide arrows on arrow layer when it is
 unreadable

---
 .../MapViewerVector/MapViewerVector.constants.ts    |  8 ++++++++
 .../MapViewerVector/utils/shapes/layer/Layer.ts     | 13 ++++++++++++-
 2 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
index ac478d7b..dcebe773 100644
--- a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
@@ -28,6 +28,14 @@ export const MAP_ELEMENT_TYPES = {
   COMPARTMENT: 'compartment',
 };
 
+export const LAYER_ELEMENT_TYPES = {
+  TEXT: 'text',
+  OVAL: 'oval',
+  RECT: 'rect',
+  LINE: 'line',
+  ARROW: 'arrow',
+};
+
 export const COMPARTMENT_SQUARE_POINTS: Array<ShapeRelAbs | ShapeRelAbsBezierPoint> = [
   {
     type: 'REL_ABS_POINT',
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
index 0554c9db..c9a8f317 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
@@ -19,6 +19,7 @@ import getArrowFeature from '@/components/Map/MapViewer/MapViewerVector/utils/sh
 import { FeatureLike } from 'ol/Feature';
 import Style from 'ol/style/Style';
 import { ArrowTypeDict, LineTypeDict } from '@/redux/shapes/shapes.types';
+import { LAYER_ELEMENT_TYPES } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 
 export interface LayerProps {
   texts: Array<LayerText>;
@@ -151,6 +152,7 @@ export default class Layer {
         geometry: polygon,
         style: polygonStyle,
         lineWidth: rect.lineWidth,
+        elementType: LAYER_ELEMENT_TYPES.RECT,
       });
       rectFeature.setStyle(this.getStyle.bind(this));
       return rectFeature;
@@ -179,6 +181,7 @@ export default class Layer {
         geometry: polygon,
         style: polygonStyle,
         lineWidth: oval.lineWidth,
+        elementType: LAYER_ELEMENT_TYPES.OVAL,
       });
       ovalFeature.setStyle(this.getStyle.bind(this));
       return ovalFeature;
@@ -227,6 +230,7 @@ export default class Layer {
           pointToProjection: this.pointToProjection,
         });
         if (startArrowFeature) {
+          startArrowFeature.set('elementType', LAYER_ELEMENT_TYPES.ARROW);
           startArrowFeature.set('lineWidth', line.width);
           startArrowFeature.setStyle(this.getStyle.bind(this));
           arrowsFeatures.push(startArrowFeature);
@@ -255,6 +259,7 @@ export default class Layer {
           pointToProjection: this.pointToProjection,
         });
         if (endArrowFeature) {
+          endArrowFeature.set('elementType', LAYER_ELEMENT_TYPES.ARROW);
           endArrowFeature.set('lineWidth', line.width);
           endArrowFeature.setStyle(this.getStyle.bind(this));
           arrowsFeatures.push(endArrowFeature);
@@ -275,6 +280,7 @@ export default class Layer {
         geometry: lineString,
         style: lineStyle,
         lineWidth: line.width,
+        elementType: LAYER_ELEMENT_TYPES.LINE,
       });
       lineFeature.setStyle(this.getStyle.bind(this));
       linesFeatures.push(lineFeature);
@@ -292,8 +298,13 @@ export default class Layer {
 
     const scale = minResolution / resolution;
     const lineWidth = feature.get('lineWidth') * scale;
+    const type = feature.get('elementType');
 
-    if (style instanceof Style && style.getStroke()) {
+    if (type === LAYER_ELEMENT_TYPES.ARROW && scale <= 0.08) {
+      return [];
+    }
+
+    if (style instanceof Style) {
       style.getStroke()?.setWidth(lineWidth);
     } else if (Array.isArray(style)) {
       style.forEach(singleStyle => {
-- 
GitLab


From 0d0c96eeec7f3e0c6be36215561d7f4b1cf7cba3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 14 Nov 2024 14:00:48 +0100
Subject: [PATCH 12/29] feat(vector-map): hide element border when it is almost
 invisible

---
 .../MapViewerVector.constants.ts              |  2 ++
 .../reactionsLayer/useOlMapReactionsLayer.ts  |  3 ++-
 .../utils/shapes/elements/BaseMultiPolygon.ts | 25 ++++++++++++++-----
 .../utils/shapes/elements/MapElement.test.ts  |  1 +
 .../utils/shapes/elements/MapElement.ts       |  3 +++
 .../utils/shapes/elements/getShapePolygon.ts  |  1 +
 .../utils/shapes/layer/Layer.ts               | 17 ++++++++++---
 7 files changed, 41 insertions(+), 11 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
index dcebe773..0cd65f9a 100644
--- a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts
@@ -2,6 +2,8 @@ import { Color, ShapeRelAbs, ShapeRelAbsBezierPoint } from '@/types/models';
 
 export const VECTOR_MAP_LAYER_TYPE = 'vectorMapLayer';
 
+export const COMPLEX_SBO_TERM = 'SBO:0000253';
+
 export const WHITE_COLOR: Color = {
   alpha: 255,
   rgb: 16777215,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index 0dca84c6..b28af94d 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -201,6 +201,7 @@ export const useOlMapReactionsLayer = ({
         validElements.push(
           new MapElement({
             id: element.id,
+            sboTerm: element.sboTerm,
             shapes: elementShapes,
             x: element.x,
             y: element.y,
@@ -255,7 +256,7 @@ export const useOlMapReactionsLayer = ({
       ...linesOverlaysFeatures,
       ...markerOverlaysFeatures,
     ];
-  }, [elements, linesOverlaysFeatures, reactions]);
+  }, [elements, linesOverlaysFeatures, markerOverlaysFeatures, reactions]);
 
   const vectorSource = useMemo(() => {
     return new VectorSource({
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
index cec6f17f..1513dd63 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
@@ -13,10 +13,14 @@ import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shape
 import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 import { Color } from '@/types/models';
-import { MAP_ELEMENT_TYPES } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
+import {
+  COMPLEX_SBO_TERM,
+  MAP_ELEMENT_TYPES,
+} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 
 export interface BaseMapElementProps {
   type: string;
+  sboTerm?: string;
   id: number;
   x: number;
   y: number;
@@ -40,6 +44,8 @@ export interface BaseMapElementProps {
 export default abstract class BaseMultiPolygon {
   type: string;
 
+  sboTerm: string | undefined;
+
   id: number;
 
   x: number;
@@ -84,6 +90,7 @@ export default abstract class BaseMultiPolygon {
 
   constructor({
     type,
+    sboTerm,
     id,
     x,
     y,
@@ -104,6 +111,7 @@ export default abstract class BaseMultiPolygon {
     pointToProjection,
   }: BaseMapElementProps) {
     this.type = type;
+    this.sboTerm = sboTerm;
     this.id = id;
     this.x = x;
     this.y = y;
@@ -202,14 +210,19 @@ export default abstract class BaseMultiPolygon {
         return;
       }
 
+      const clonedStyle = style.clone();
       if (type === 'text') {
-        style.getText()?.setScale(textScale);
-        style.getText()?.setText(text);
+        clonedStyle.getText()?.setScale(textScale);
+        clonedStyle.getText()?.setText(text);
       }
-      if (style.getStroke() && lineWidth) {
-        style.getStroke()?.setWidth(lineWidth * textScale);
+      if (clonedStyle.getStroke() && lineWidth) {
+        if (lineWidth * textScale < 0.08 && this.sboTerm !== COMPLEX_SBO_TERM) {
+          clonedStyle.setStroke(null);
+        } else {
+          clonedStyle.getStroke()?.setWidth(lineWidth * textScale);
+        }
       }
-      styles.push(style);
+      styles.push(clonedStyle);
     });
     return styles;
   }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
index 71c5c3c2..81665cc6 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
@@ -40,6 +40,7 @@ describe('MapElement', () => {
     });
     props = {
       id: 1,
+      sboTerm: 'SBO:2313123',
       shapes: shapesFixture,
       x: 0,
       y: 0,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
index 909c7c6b..575d8d77 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
@@ -31,6 +31,7 @@ import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/
 
 export type MapElementProps = {
   id: number;
+  sboTerm: string;
   shapes: Array<Shape>;
   x: number;
   y: number;
@@ -89,6 +90,7 @@ export default class MapElement extends BaseMultiPolygon {
 
   constructor({
     id,
+    sboTerm,
     shapes,
     x,
     y,
@@ -121,6 +123,7 @@ export default class MapElement extends BaseMultiPolygon {
   }: MapElementProps) {
     super({
       type: FEATURE_TYPE.ALIAS,
+      sboTerm,
       id,
       x,
       y,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getShapePolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getShapePolygon.ts
index ef748554..7e2f7a59 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getShapePolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getShapePolygon.ts
@@ -48,6 +48,7 @@ export default function getShapePolygon({
       return [mirroredX, coord[1]];
     });
   }
+  coords.push(coords[0]);
 
   return new Polygon([coords]);
 }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
index c9a8f317..6d17b6b1 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
@@ -289,6 +289,7 @@ export default class Layer {
   };
 
   protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
+    const styles: Array<Style> = [];
     const maxZoom = this.mapInstance?.getView().get('originalMaxZoom');
     const minResolution = this.mapInstance?.getView().getResolutionForZoom(maxZoom);
     const style = feature.get('style');
@@ -303,16 +304,24 @@ export default class Layer {
     if (type === LAYER_ELEMENT_TYPES.ARROW && scale <= 0.08) {
       return [];
     }
-
+    let clonedStyle: Style;
     if (style instanceof Style) {
-      style.getStroke()?.setWidth(lineWidth);
+      clonedStyle = style.clone();
+      clonedStyle.getStroke()?.setWidth(lineWidth);
+      styles.push(clonedStyle);
     } else if (Array.isArray(style)) {
       style.forEach(singleStyle => {
         if (singleStyle instanceof Style && singleStyle.getStroke()) {
-          singleStyle.getStroke()?.setWidth(lineWidth);
+          clonedStyle = singleStyle.clone();
+          if (lineWidth < 0.8) {
+            clonedStyle.getStroke()?.setWidth(lineWidth);
+          } else {
+            clonedStyle.setStroke(null);
+          }
+          styles.push(clonedStyle);
         }
       });
     }
-    return style;
+    return styles;
   }
 }
-- 
GitLab


From 7ffb101013d79f4ee5eb61d6b229133fa64d522c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 14 Nov 2024 14:24:00 +0100
Subject: [PATCH 13/29] fix(vector-map): correct incorrect bioEntity reducer
 assignment

---
 src/redux/bioEntity/bioEntity.reducers.ts | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/redux/bioEntity/bioEntity.reducers.ts b/src/redux/bioEntity/bioEntity.reducers.ts
index f5f4f94c..57c38c9d 100644
--- a/src/redux/bioEntity/bioEntity.reducers.ts
+++ b/src/redux/bioEntity/bioEntity.reducers.ts
@@ -141,18 +141,18 @@ export const setMultipleBioEntityContentsReducer = (
   state: BioEntityContentsState,
   action: PayloadAction<Array<BioEntity>>,
 ): void => {
-  state.data = [
-    {
-      data: action.payload.map(bioEntity => {
-        return {
+  state.data = action.payload.map(bioEntity => {
+    return {
+      data: [
+        {
           bioEntity,
           perfect: true,
-        };
-      }),
+        },
+      ],
+      searchQueryElement: bioEntity.id.toString(),
       loading: 'succeeded',
       error: DEFAULT_ERROR,
-      searchQueryElement: 'asd',
-    },
-  ];
+    };
+  });
   state.loading = 'succeeded';
 };
-- 
GitLab


From 4a4bd9517626158c17ab3f2102efa3580bfa76af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 14 Nov 2024 15:18:53 +0100
Subject: [PATCH 14/29] fix(vector-map): remove border disabling in Layer
 getStyle function

---
 .../MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
index 6d17b6b1..dd8b81dc 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
@@ -311,13 +311,9 @@ export default class Layer {
       styles.push(clonedStyle);
     } else if (Array.isArray(style)) {
       style.forEach(singleStyle => {
-        if (singleStyle instanceof Style && singleStyle.getStroke()) {
+        if (singleStyle instanceof Style) {
           clonedStyle = singleStyle.clone();
-          if (lineWidth < 0.8) {
-            clonedStyle.getStroke()?.setWidth(lineWidth);
-          } else {
-            clonedStyle.setStroke(null);
-          }
+          clonedStyle.getStroke()?.setWidth(lineWidth);
           styles.push(clonedStyle);
         }
       });
-- 
GitLab


From 43460d9b74e03bde46473c53d8dff27f81d292e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 14 Nov 2024 15:29:39 +0100
Subject: [PATCH 15/29] feat(vector-map): scale element dashed border

---
 .../utils/shapes/elements/BaseMultiPolygon.ts | 23 +++++++++++--------
 .../utils/shapes/layer/Layer.ts               | 13 ++++-------
 .../utils/shapes/reaction/Reaction.ts         | 11 ++++-----
 .../shapes/style/getScaledElementStyle.ts     | 17 ++++++++++++++
 4 files changed, 40 insertions(+), 24 deletions(-)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
index 1513dd63..9157e7a5 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
@@ -166,7 +166,7 @@ export default abstract class BaseMultiPolygon {
   protected drawMultiPolygonFeature(mapInstance: MapInstance): void {
     this.feature = new Feature({
       geometry: new MultiPolygon(this.polygons),
-      getTextScale: (resolution: number): number => {
+      getScale: (resolution: number): number => {
         const maxZoom = mapInstance?.getView().get('originalMaxZoom');
         if (maxZoom) {
           const minResolution = mapInstance?.getView().getResolutionForZoom(maxZoom);
@@ -185,10 +185,10 @@ export default abstract class BaseMultiPolygon {
 
   protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
     const styles: Array<Style> = [];
-    const getTextScale = feature.get('getTextScale');
-    let textScale = 1;
-    if (getTextScale instanceof Function) {
-      textScale = getTextScale(resolution);
+    const getScale = feature.get('getScale');
+    let scale = 1;
+    if (getScale instanceof Function) {
+      scale = getScale(resolution);
     }
 
     let type: string;
@@ -205,21 +205,26 @@ export default abstract class BaseMultiPolygon {
       }
       if (
         [MAP_ELEMENT_TYPES.MODIFICATION, MAP_ELEMENT_TYPES.TEXT].includes(type) &&
-        textScale * fontSize <= 4
+        scale * fontSize <= 4
       ) {
         return;
       }
 
       const clonedStyle = style.clone();
       if (type === 'text') {
-        clonedStyle.getText()?.setScale(textScale);
+        clonedStyle.getText()?.setScale(scale);
         clonedStyle.getText()?.setText(text);
       }
       if (clonedStyle.getStroke() && lineWidth) {
-        if (lineWidth * textScale < 0.08 && this.sboTerm !== COMPLEX_SBO_TERM) {
+        if (lineWidth * scale < 0.08 && this.sboTerm !== COMPLEX_SBO_TERM) {
           clonedStyle.setStroke(null);
         } else {
-          clonedStyle.getStroke()?.setWidth(lineWidth * textScale);
+          clonedStyle.getStroke()?.setWidth(lineWidth * scale);
+          const lineDash = clonedStyle.getStroke()?.getLineDash();
+          if (lineDash) {
+            const newLineDash = lineDash.map(width => width * scale);
+            clonedStyle.getStroke()?.setLineDash(newLineDash);
+          }
         }
       }
       styles.push(clonedStyle);
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
index dd8b81dc..9a91384f 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts
@@ -20,6 +20,7 @@ import { FeatureLike } from 'ol/Feature';
 import Style from 'ol/style/Style';
 import { ArrowTypeDict, LineTypeDict } from '@/redux/shapes/shapes.types';
 import { LAYER_ELEMENT_TYPES } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
+import getScaledElementStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle';
 
 export interface LayerProps {
   texts: Array<LayerText>;
@@ -298,23 +299,19 @@ export default class Layer {
     }
 
     const scale = minResolution / resolution;
-    const lineWidth = feature.get('lineWidth') * scale;
+    const lineWidth = feature.get('lineWidth');
     const type = feature.get('elementType');
 
     if (type === LAYER_ELEMENT_TYPES.ARROW && scale <= 0.08) {
       return [];
     }
-    let clonedStyle: Style;
+
     if (style instanceof Style) {
-      clonedStyle = style.clone();
-      clonedStyle.getStroke()?.setWidth(lineWidth);
-      styles.push(clonedStyle);
+      styles.push(getScaledElementStyle(style, lineWidth, scale));
     } else if (Array.isArray(style)) {
       style.forEach(singleStyle => {
         if (singleStyle instanceof Style) {
-          clonedStyle = singleStyle.clone();
-          clonedStyle.getStroke()?.setWidth(lineWidth);
-          styles.push(clonedStyle);
+          styles.push(getScaledElementStyle(singleStyle, lineWidth, scale));
         }
       });
     }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts
index c4fc7499..f8140764 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts
@@ -19,6 +19,7 @@ import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shape
 import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
 import { ArrowTypeDict, LineTypeDict } from '@/redux/shapes/shapes.types';
 import { FEATURE_TYPE } from '@/constants/features';
+import getScaledElementStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle';
 
 export interface ReactionProps {
   id: number;
@@ -332,7 +333,7 @@ export default class Reaction {
     }
 
     const scale = minResolution / resolution;
-    const lineWidth = feature.get('lineWidth') * scale;
+    const lineWidth = feature.get('lineWidth');
     const type = feature.get('elementType');
     const fontSize = feature.get('fontSize');
 
@@ -344,15 +345,11 @@ export default class Reaction {
     }
 
     if (style instanceof Style) {
-      style.getStroke()?.setWidth(lineWidth);
-      style.getText()?.setScale(scale);
-      styles.push(style);
+      styles.push(getScaledElementStyle(style, lineWidth, scale));
     } else if (Array.isArray(style)) {
       style.forEach(singleStyle => {
         if (singleStyle instanceof Style) {
-          singleStyle.getStroke()?.setWidth(lineWidth);
-          singleStyle.getText()?.setScale(scale);
-          styles.push(singleStyle);
+          styles.push(getScaledElementStyle(singleStyle, lineWidth, scale));
         }
       });
     }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts
new file mode 100644
index 00000000..f4a4f77a
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts
@@ -0,0 +1,17 @@
+import Style from 'ol/style/Style';
+
+export default function getScaledElementStyle(
+  style: Style,
+  lineWidth: number,
+  scale: number,
+): Style {
+  const clonedStyle = style.clone();
+  const lineDash = clonedStyle.getStroke()?.getLineDash();
+  if (lineDash) {
+    const newLineDash = lineDash.map(width => width * scale);
+    clonedStyle.getStroke()?.setLineDash(newLineDash);
+  }
+  clonedStyle.getStroke()?.setWidth(lineWidth * scale);
+  clonedStyle.getText()?.setScale(scale);
+  return clonedStyle;
+}
-- 
GitLab


From 36bcfb412b03dbbaef624d31358f6d29670237ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Fri, 15 Nov 2024 11:08:24 +0100
Subject: [PATCH 16/29] feat(vector-map): add spinner when diagram is loading

---
 src/components/Map/Map.component.tsx          |   2 +
 .../MapLoader/MapLoader.component.test.tsx    | 107 ++++++++++++++++++
 .../Map/MapLoader/MapLoader.component.tsx     |  50 ++++++++
 .../Map/MapLoader/MapLoader.styles.css        |   9 ++
 .../reactionsLayer/useOlMapReactionsLayer.ts  |   6 +-
 src/models/mocks/modelsMock.ts                |   1 +
 src/redux/layers/layers.selectors.ts          |   2 +
 .../modelElements/modelElements.selector.ts   |   5 +
 .../newReactions/newReactions.selectors.ts    |   5 +
 src/redux/shapes/shapes.selectors.ts          |  15 +++
 10 files changed, 200 insertions(+), 2 deletions(-)
 create mode 100644 src/components/Map/MapLoader/MapLoader.component.test.tsx
 create mode 100644 src/components/Map/MapLoader/MapLoader.component.tsx
 create mode 100644 src/components/Map/MapLoader/MapLoader.styles.css

diff --git a/src/components/Map/Map.component.tsx b/src/components/Map/Map.component.tsx
index 67d4d216..67b7187c 100644
--- a/src/components/Map/Map.component.tsx
+++ b/src/components/Map/Map.component.tsx
@@ -2,6 +2,7 @@
 import { Drawer } from '@/components/Map/Drawer';
 import { Legend } from '@/components/Map/Legend';
 import { MapViewer } from '@/components/Map/MapViewer';
+import { MapLoader } from '@/components/Map/MapLoader/MapLoader.component';
 import { MapAdditionalActions } from './MapAdditionalActions';
 import { MapAdditionalOptions } from './MapAdditionalOptions';
 import { PluginsDrawer } from './PluginsDrawer';
@@ -18,6 +19,7 @@ export const Map = (): JSX.Element => {
       <PluginsDrawer />
       <Legend />
       <MapAdditionalActions />
+      <MapLoader />
     </div>
   );
 };
diff --git a/src/components/Map/MapLoader/MapLoader.component.test.tsx b/src/components/Map/MapLoader/MapLoader.component.test.tsx
new file mode 100644
index 00000000..eb35db6b
--- /dev/null
+++ b/src/components/Map/MapLoader/MapLoader.component.test.tsx
@@ -0,0 +1,107 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import {
+  getReduxWrapperWithStore,
+  InitialStoreState,
+} from '@/utils/testing/getReduxWrapperWithStore';
+import { StoreType } from '@/redux/store';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { newReactionsLoadingSelector } from '@/redux/newReactions/newReactions.selectors';
+import { modelElementsLoadingSelector } from '@/redux/modelElements/modelElements.selector';
+import { isDrawerOpenSelector } from '@/redux/drawer/drawer.selectors';
+import { vectorRenderingSelector } from '@/redux/models/models.selectors';
+import {
+  arrowTypesLoadingSelector,
+  bioShapesLoadingSelector,
+  lineTypesLoadingSelector,
+} from '@/redux/shapes/shapes.selectors';
+import { layersLoadingSelector } from '@/redux/layers/layers.selectors';
+import { MapLoader } from './MapLoader.component';
+
+jest.mock('../../../redux/hooks/useAppSelector', () => ({
+  useAppSelector: jest.fn(),
+}));
+type SelectorFunction = (state: never) => string | boolean;
+
+const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
+  const { Wrapper, store } = getReduxWrapperWithStore(initialStore);
+
+  return (
+    render(
+      <Wrapper>
+        <MapLoader />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+describe('MapLoader', () => {
+  const mockUseAppSelector = useAppSelector as jest.Mock;
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should not render the LoadingIndicator when no data is loading', () => {
+    mockUseAppSelector.mockImplementation(selector => {
+      const selectorMap = new Map<SelectorFunction, string | boolean>([
+        [newReactionsLoadingSelector, 'succeeded'],
+        [modelElementsLoadingSelector, 'succeeded'],
+        [vectorRenderingSelector, true],
+        [bioShapesLoadingSelector, 'succeeded'],
+        [lineTypesLoadingSelector, 'succeeded'],
+        [arrowTypesLoadingSelector, 'succeeded'],
+        [layersLoadingSelector, 'succeeded'],
+        [isDrawerOpenSelector, false],
+      ]);
+
+      return selectorMap.get(selector) ?? false;
+    });
+    renderComponent();
+
+    expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
+  });
+
+  it('should render the LoadingIndicator when vectorRendering is true and data is loading', () => {
+    mockUseAppSelector.mockImplementation(selector => {
+      const selectorMap = new Map<SelectorFunction, string | boolean>([
+        [newReactionsLoadingSelector, 'pending'],
+        [modelElementsLoadingSelector, 'succeeded'],
+        [vectorRenderingSelector, true],
+        [bioShapesLoadingSelector, 'succeeded'],
+        [lineTypesLoadingSelector, 'succeeded'],
+        [arrowTypesLoadingSelector, 'succeeded'],
+        [layersLoadingSelector, 'succeeded'],
+        [isDrawerOpenSelector, false],
+      ]);
+
+      return selectorMap.get(selector) ?? false;
+    });
+    renderComponent();
+
+    expect(screen.queryByTestId('loading-indicator')).toBeInTheDocument();
+  });
+
+  it('should not render the LoadingIndicator when vectorRendering is false even when data is loading', () => {
+    mockUseAppSelector.mockImplementation(selector => {
+      const selectorMap = new Map<SelectorFunction, string | boolean>([
+        [newReactionsLoadingSelector, 'pending'],
+        [modelElementsLoadingSelector, 'succeeded'],
+        [vectorRenderingSelector, false],
+        [bioShapesLoadingSelector, 'succeeded'],
+        [lineTypesLoadingSelector, 'succeeded'],
+        [arrowTypesLoadingSelector, 'succeeded'],
+        [layersLoadingSelector, 'succeeded'],
+        [isDrawerOpenSelector, false],
+      ]);
+
+      return selectorMap.get(selector) ?? false;
+    });
+    renderComponent();
+
+    expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
+  });
+});
diff --git a/src/components/Map/MapLoader/MapLoader.component.tsx b/src/components/Map/MapLoader/MapLoader.component.tsx
new file mode 100644
index 00000000..fc457e0b
--- /dev/null
+++ b/src/components/Map/MapLoader/MapLoader.component.tsx
@@ -0,0 +1,50 @@
+import { LoadingIndicator } from '@/shared/LoadingIndicator';
+import { useMemo } from 'react';
+import { newReactionsLoadingSelector } from '@/redux/newReactions/newReactions.selectors';
+import { modelElementsLoadingSelector } from '@/redux/modelElements/modelElements.selector';
+import { vectorRenderingSelector } from '@/redux/models/models.selectors';
+import {
+  arrowTypesLoadingSelector,
+  bioShapesLoadingSelector,
+  lineTypesLoadingSelector,
+} from '@/redux/shapes/shapes.selectors';
+import { layersLoadingSelector } from '@/redux/layers/layers.selectors';
+import './MapLoader.styles.css';
+import { isDrawerOpenSelector } from '@/redux/drawer/drawer.selectors';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+
+export const MapLoader = (): JSX.Element => {
+  const reactionsFetching = useAppSelector(newReactionsLoadingSelector);
+  const modelElementsFetching = useAppSelector(modelElementsLoadingSelector);
+  const vectorRendering = useAppSelector(vectorRenderingSelector);
+  const bioShapesFetching = useAppSelector(bioShapesLoadingSelector);
+  const lineTypesFetching = useAppSelector(lineTypesLoadingSelector);
+  const arrowTypesFetching = useAppSelector(arrowTypesLoadingSelector);
+  const layersLoading = useAppSelector(layersLoadingSelector);
+
+  const isDrawerOpen = useAppSelector(isDrawerOpenSelector);
+
+  const showLoader = useMemo(() => {
+    return [
+      reactionsFetching,
+      modelElementsFetching,
+      bioShapesFetching,
+      lineTypesFetching,
+      arrowTypesFetching,
+      layersLoading,
+    ].includes('pending');
+  }, [
+    reactionsFetching,
+    modelElementsFetching,
+    bioShapesFetching,
+    lineTypesFetching,
+    arrowTypesFetching,
+    layersLoading,
+  ]);
+
+  return (
+    <div className={`map-loader transition-all duration-500 ${isDrawerOpen ? 'move-right' : ''}`}>
+      {vectorRendering && showLoader && <LoadingIndicator width={48} height={48} />}
+    </div>
+  );
+};
diff --git a/src/components/Map/MapLoader/MapLoader.styles.css b/src/components/Map/MapLoader/MapLoader.styles.css
new file mode 100644
index 00000000..750e4cb6
--- /dev/null
+++ b/src/components/Map/MapLoader/MapLoader.styles.css
@@ -0,0 +1,9 @@
+.map-loader {
+  position: absolute;
+  left: 120px;
+  top: 128px;
+}
+
+.map-loader.move-right {
+  left: 550px;
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index b28af94d..d8cad731 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -64,8 +64,10 @@ export const useOlMapReactionsLayer = ({
   const pointToProjection = usePointToProjection();
 
   useEffect(() => {
-    dispatch(getModelElements(currentModelId));
-    dispatch(getNewReactions(currentModelId));
+    if (currentModelId) {
+      dispatch(getModelElements(currentModelId));
+      dispatch(getNewReactions(currentModelId));
+    }
   }, [currentModelId, dispatch]);
 
   const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
diff --git a/src/models/mocks/modelsMock.ts b/src/models/mocks/modelsMock.ts
index 1684bf93..53dac6b9 100644
--- a/src/models/mocks/modelsMock.ts
+++ b/src/models/mocks/modelsMock.ts
@@ -474,6 +474,7 @@ export const CORE_PD_MODEL_MOCK: MapModel = {
   modificationDates: [],
   minZoom: 2,
   maxZoom: 9,
+  vectorRendering: true,
 };
 
 export const MODEL_WITH_DESCRIPTION: MapModel = {
diff --git a/src/redux/layers/layers.selectors.ts b/src/redux/layers/layers.selectors.ts
index 987ec4ac..d29698e0 100644
--- a/src/redux/layers/layers.selectors.ts
+++ b/src/redux/layers/layers.selectors.ts
@@ -6,6 +6,8 @@ export const layersSelector = createSelector(
   state => state.layers?.data?.layers || [],
 );
 
+export const layersLoadingSelector = createSelector(rootSelector, state => state.layers.loading);
+
 export const layersVisibilitySelector = createSelector(
   rootSelector,
   state => state.layers?.data?.layersVisibility || {},
diff --git a/src/redux/modelElements/modelElements.selector.ts b/src/redux/modelElements/modelElements.selector.ts
index 54b4a75b..3a9d8b54 100644
--- a/src/redux/modelElements/modelElements.selector.ts
+++ b/src/redux/modelElements/modelElements.selector.ts
@@ -5,3 +5,8 @@ export const modelElementsSelector = createSelector(
   rootSelector,
   state => state.modelElements.data,
 );
+
+export const modelElementsLoadingSelector = createSelector(
+  rootSelector,
+  state => state.modelElements.loading,
+);
diff --git a/src/redux/newReactions/newReactions.selectors.ts b/src/redux/newReactions/newReactions.selectors.ts
index 4dc2babe..146bbc85 100644
--- a/src/redux/newReactions/newReactions.selectors.ts
+++ b/src/redux/newReactions/newReactions.selectors.ts
@@ -3,6 +3,11 @@ import { rootSelector } from '../root/root.selectors';
 
 export const newReactionsSelector = createSelector(rootSelector, state => state.newReactions);
 
+export const newReactionsLoadingSelector = createSelector(
+  newReactionsSelector,
+  state => state.loading,
+);
+
 export const newReactionsDataSelector = createSelector(
   newReactionsSelector,
   reactions => reactions.data || [],
diff --git a/src/redux/shapes/shapes.selectors.ts b/src/redux/shapes/shapes.selectors.ts
index cdd4fa16..d23c9a97 100644
--- a/src/redux/shapes/shapes.selectors.ts
+++ b/src/redux/shapes/shapes.selectors.ts
@@ -8,12 +8,27 @@ export const bioShapesSelector = createSelector(
   shapes => shapes.bioShapesState.data,
 );
 
+export const bioShapesLoadingSelector = createSelector(
+  shapesSelector,
+  shapes => shapes.bioShapesState.loading,
+);
+
 export const lineTypesSelector = createSelector(
   shapesSelector,
   shapes => shapes.lineTypesState.data || {},
 );
 
+export const lineTypesLoadingSelector = createSelector(
+  shapesSelector,
+  shapes => shapes.lineTypesState.loading,
+);
+
 export const arrowTypesSelector = createSelector(
   shapesSelector,
   shapes => shapes.arrowTypesState.data || {},
 );
+
+export const arrowTypesLoadingSelector = createSelector(
+  shapesSelector,
+  shapes => shapes.arrowTypesState.loading,
+);
-- 
GitLab


From 1b2a9230d25639cb64df3bdb56c54eb82ebb3da6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Mon, 18 Nov 2024 11:37:48 +0100
Subject: [PATCH 17/29] feat(vector-map): Optimize layers to avoid out of
 memory error

---
 .../reactionsLayer/processModelElements.ts    | 120 ++++++++++++++++
 .../reactionsLayer/useOlMapReactionsLayer.ts  | 132 ++++--------------
 .../utils/config/useOlMapVectorLayers.ts      |   7 +-
 .../config/pinsLayer/useOlMapPinsLayer.ts     |  17 ++-
 .../reactionsLayer/useOlMapReactionsLayer.ts  |  22 +--
 .../utils/config/useOlMapCommonLayers.ts      |   5 +-
 src/utils/useDebouncedValue.test.ts           |  50 +++++++
 src/utils/useDebouncedValue.ts                |  15 ++
 8 files changed, 237 insertions(+), 131 deletions(-)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
 create mode 100644 src/utils/useDebouncedValue.test.ts
 create mode 100644 src/utils/useDebouncedValue.ts

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
new file mode 100644
index 00000000..8aba562e
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
@@ -0,0 +1,120 @@
+import { ModelElement, ModelElements } from '@/types/models';
+import MapElement from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement';
+import CompartmentCircle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle';
+import CompartmentSquare from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare';
+import CompartmentPathway from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway';
+import Glyph from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph';
+import {
+  HorizontalAlign,
+  VerticalAlign,
+} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
+import { MapInstance } from '@/types/map';
+import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
+import { BioShapesDict, LineTypeDict } from '@/redux/shapes/shapes.types';
+import { OverlayOrder } from '@/redux/overlayBioEntity/overlayBioEntity.utils';
+import { OverlayBioEntityRender } from '@/types/OLrendering';
+import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
+
+export default function processModelElements(
+  modelElements: ModelElements,
+  shapes: BioShapesDict,
+  lineTypes: LineTypeDict,
+  groupedElementsOverlays: Record<string, Array<OverlayBioEntityRender>>,
+  overlaysOrder: Array<OverlayOrder>,
+  getOverlayColor: GetOverlayBioEntityColorByAvailableProperties,
+  mapInstance: MapInstance,
+  pointToProjection: UsePointToProjectionResult,
+): Array<MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph> {
+  const validElements: Array<
+    MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph
+  > = [];
+  modelElements.content.forEach((element: ModelElement) => {
+    if (element.glyph) {
+      const glyph = new Glyph({
+        id: element.glyph.id,
+        x: element.x,
+        y: element.y,
+        width: element.width,
+        height: element.height,
+        zIndex: element.z,
+        pointToProjection,
+        mapInstance,
+      });
+      validElements.push(glyph);
+      return;
+    }
+
+    if (element.sboTerm === 'SBO:0000290') {
+      const compartmentProps = {
+        id: element.id,
+        x: element.x,
+        y: element.y,
+        nameX: element.nameX,
+        nameY: element.nameY,
+        nameHeight: element.nameHeight,
+        nameWidth: element.nameWidth,
+        width: element.width,
+        height: element.height,
+        zIndex: element.z,
+        innerWidth: element.innerWidth,
+        outerWidth: element.outerWidth,
+        thickness: element.thickness,
+        fontColor: element.fontColor,
+        fillColor: element.fillColor,
+        borderColor: element.borderColor,
+        nameVerticalAlign: element.nameVerticalAlign as VerticalAlign,
+        nameHorizontalAlign: element.nameHorizontalAlign as HorizontalAlign,
+        text: element.name,
+        fontSize: element.fontSize,
+        pointToProjection,
+        mapInstance,
+      };
+      if (element.shape === 'OVAL_COMPARTMENT') {
+        validElements.push(new CompartmentCircle(compartmentProps));
+      } else if (element.shape === 'SQUARE_COMPARTMENT') {
+        validElements.push(new CompartmentSquare(compartmentProps));
+      } else if (element.shape === 'PATHWAY') {
+        validElements.push(new CompartmentPathway(compartmentProps));
+      }
+      return;
+    }
+    const elementShapes = shapes[element.sboTerm];
+    if (elementShapes) {
+      validElements.push(
+        new MapElement({
+          id: element.id,
+          shapes: elementShapes,
+          x: element.x,
+          y: element.y,
+          nameX: element.nameX,
+          nameY: element.nameY,
+          nameHeight: element.nameHeight,
+          nameWidth: element.nameWidth,
+          width: element.width,
+          height: element.height,
+          zIndex: element.z,
+          lineWidth: element.lineWidth,
+          lineType: element.borderLineType,
+          fontColor: element.fontColor,
+          fillColor: element.fillColor,
+          borderColor: element.borderColor,
+          nameVerticalAlign: element.nameVerticalAlign as VerticalAlign,
+          nameHorizontalAlign: element.nameHorizontalAlign as HorizontalAlign,
+          homodimer: element.homodimer,
+          activity: element.activity,
+          text: element.name,
+          fontSize: element.fontSize,
+          pointToProjection,
+          mapInstance,
+          modifications: element.modificationResidues,
+          lineTypes,
+          bioShapes: shapes,
+          overlays: groupedElementsOverlays[element.id],
+          overlaysOrder,
+          getOverlayColor,
+        }),
+      );
+    }
+  });
+  return validElements;
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index c879e113..87f31fcf 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -12,17 +12,12 @@ import {
   lineTypesSelector,
 } from '@/redux/shapes/shapes.selectors';
 import { MapInstance } from '@/types/map';
-import {
-  HorizontalAlign,
-  VerticalAlign,
-} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
 import { modelElementsSelector } from '@/redux/modelElements/modelElements.selector';
 import { currentModelIdSelector } from '@/redux/models/models.selectors';
 import { getModelElements } from '@/redux/modelElements/modelElements.thunks';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import CompartmentSquare from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare';
 import CompartmentCircle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle';
-import { ModelElement } from '@/types/models';
 import Glyph from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph';
 import CompartmentPathway from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway';
 import Reaction from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction';
@@ -41,6 +36,8 @@ import LineOverlay from '@/components/Map/MapViewer/MapViewerVector/utils/shapes
 import { markersSufraceOfCurrentMapDataSelector } from '@/redux/markers/markers.selectors';
 import { parseSurfaceMarkersToBioEntityRender } from '@/components/Map/MapViewer/utils/config/overlaysLayer/parseSurfaceMarkersToBioEntityRender';
 import MarkerOverlay from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay';
+import processModelElements from '@/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements';
+import useDebouncedValue from '@/utils/useDebouncedValue';
 
 export const useOlMapReactionsLayer = ({
   mapInstance,
@@ -58,7 +55,8 @@ export const useOlMapReactionsLayer = ({
   const overlaysOrder = useSelector(getOverlayOrderSelector);
   const currentMarkers = useAppSelector(markersSufraceOfCurrentMapDataSelector);
   const markersRender = parseSurfaceMarkersToBioEntityRender(currentMarkers);
-
+  const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
+  const debouncedBioEntities = useDebouncedValue(bioEntities, 2000);
   const { getOverlayBioEntityColorByAvailableProperties } = useGetOverlayColor();
 
   const pointToProjection = usePointToProjection();
@@ -68,13 +66,13 @@ export const useOlMapReactionsLayer = ({
     dispatch(getNewReactions(currentModelId));
   }, [currentModelId, dispatch]);
 
-  const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
-
   const groupedElementsOverlays = useMemo(() => {
-    const elementsBioEntitesOverlay = bioEntities.filter(bioEntity => bioEntity.type !== 'line');
+    const elementsBioEntitesOverlay = debouncedBioEntities.filter(
+      bioEntity => bioEntity.type !== 'line',
+    );
     const grouped = groupBy(elementsBioEntitesOverlay, bioEntity => bioEntity.id.toString());
     return getOverlays(grouped, getOverlayBioEntityColorByAvailableProperties);
-  }, [bioEntities, getOverlayBioEntityColorByAvailableProperties]);
+  }, [debouncedBioEntities, getOverlayBioEntityColorByAvailableProperties]);
 
   const linesOverlays = useMemo(() => {
     return bioEntities.filter(bioEntity => bioEntity.type === 'line');
@@ -142,99 +140,16 @@ export const useOlMapReactionsLayer = ({
     if (!modelElements || !shapes) {
       return [];
     }
-
-    const validElements: Array<
-      MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph
-    > = [];
-    modelElements.content.forEach((element: ModelElement) => {
-      if (element.glyph) {
-        const glyph = new Glyph({
-          id: element.glyph.id,
-          x: element.x,
-          y: element.y,
-          width: element.width,
-          height: element.height,
-          zIndex: element.z,
-          pointToProjection,
-          mapInstance,
-        });
-        validElements.push(glyph);
-        return;
-      }
-
-      if (element.sboTerm === 'SBO:0000290') {
-        const compartmentProps = {
-          id: element.id,
-          x: element.x,
-          y: element.y,
-          nameX: element.nameX,
-          nameY: element.nameY,
-          nameHeight: element.nameHeight,
-          nameWidth: element.nameWidth,
-          width: element.width,
-          height: element.height,
-          zIndex: element.z,
-          innerWidth: element.innerWidth,
-          outerWidth: element.outerWidth,
-          thickness: element.thickness,
-          fontColor: element.fontColor,
-          fillColor: element.fillColor,
-          borderColor: element.borderColor,
-          nameVerticalAlign: element.nameVerticalAlign as VerticalAlign,
-          nameHorizontalAlign: element.nameHorizontalAlign as HorizontalAlign,
-          text: element.name,
-          fontSize: element.fontSize,
-          pointToProjection,
-          mapInstance,
-        };
-        if (element.shape === 'OVAL_COMPARTMENT') {
-          validElements.push(new CompartmentCircle(compartmentProps));
-        } else if (element.shape === 'SQUARE_COMPARTMENT') {
-          validElements.push(new CompartmentSquare(compartmentProps));
-        } else if (element.shape === 'PATHWAY') {
-          validElements.push(new CompartmentPathway(compartmentProps));
-        }
-        return;
-      }
-      const elementShapes = shapes[element.sboTerm];
-      if (elementShapes) {
-        validElements.push(
-          new MapElement({
-            id: element.id,
-            shapes: elementShapes,
-            x: element.x,
-            y: element.y,
-            nameX: element.nameX,
-            nameY: element.nameY,
-            nameHeight: element.nameHeight,
-            nameWidth: element.nameWidth,
-            width: element.width,
-            height: element.height,
-            zIndex: element.z,
-            lineWidth: element.lineWidth,
-            lineType: element.borderLineType,
-            fontColor: element.fontColor,
-            fillColor: element.fillColor,
-            borderColor: element.borderColor,
-            nameVerticalAlign: element.nameVerticalAlign as VerticalAlign,
-            nameHorizontalAlign: element.nameHorizontalAlign as HorizontalAlign,
-            homodimer: element.homodimer,
-            activity: element.activity,
-            text: element.name,
-            fontSize: element.fontSize,
-            pointToProjection,
-            mapInstance,
-            modifications: element.modificationResidues,
-            lineTypes,
-            bioShapes: shapes,
-            overlays: groupedElementsOverlays[element.id],
-            overlaysOrder,
-            getOverlayColor: getOverlayBioEntityColorByAvailableProperties,
-          }),
-        );
-      }
-    });
-    return validElements;
+    return processModelElements(
+      modelElements,
+      shapes,
+      lineTypes,
+      groupedElementsOverlays,
+      overlaysOrder,
+      getOverlayBioEntityColorByAvailableProperties,
+      mapInstance,
+      pointToProjection,
+    );
   }, [
     modelElements,
     shapes,
@@ -257,11 +172,12 @@ export const useOlMapReactionsLayer = ({
     ];
   }, [elements, linesOverlaysFeatures, markerOverlaysFeatures, reactions]);
 
-  const vectorSource = useMemo(() => {
-    return new VectorSource({
-      features,
-    });
-  }, [features]);
+  const vectorSource = useMemo(() => new VectorSource(), []);
+
+  useEffect(() => {
+    vectorSource.clear();
+    vectorSource.addFeatures(features);
+  }, [features, vectorSource]);
 
   return useMemo(() => {
     const vectorLayer = new VectorLayer({
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts
index 7ebb6e70..cebab4e0 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts
@@ -2,8 +2,9 @@
 import { MapInstance } from '@/types/map';
 import { useOlMapWhiteCardLayer } from '@/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer';
 import { useOlMapAdditionalLayers } from '@/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers';
-import { MapConfig } from '../../MapViewerVector.types';
+import { useMemo } from 'react';
 import { useOlMapReactionsLayer } from './reactionsLayer/useOlMapReactionsLayer';
+import { MapConfig } from '../../MapViewerVector.types';
 
 interface UseOlMapLayersInput {
   mapInstance: MapInstance;
@@ -14,5 +15,7 @@ export const useOlMapVectorLayers = ({ mapInstance }: UseOlMapLayersInput): MapC
   const whiteCardLayer = useOlMapWhiteCardLayer();
   const additionalLayers = useOlMapAdditionalLayers(mapInstance);
 
-  return [whiteCardLayer, reactionsLayer, ...additionalLayers];
+  return useMemo(() => {
+    return [whiteCardLayer, reactionsLayer, ...additionalLayers];
+  }, [whiteCardLayer, reactionsLayer, additionalLayers]);
 };
diff --git a/src/components/Map/MapViewer/utils/config/pinsLayer/useOlMapPinsLayer.ts b/src/components/Map/MapViewer/utils/config/pinsLayer/useOlMapPinsLayer.ts
index 75d8c838..641b537d 100644
--- a/src/components/Map/MapViewer/utils/config/pinsLayer/useOlMapPinsLayer.ts
+++ b/src/components/Map/MapViewer/utils/config/pinsLayer/useOlMapPinsLayer.ts
@@ -11,7 +11,7 @@ import Feature from 'ol/Feature';
 import { Geometry } from 'ol/geom';
 import VectorLayer from 'ol/layer/Vector';
 import VectorSource from 'ol/source/Vector';
-import { useCallback, useMemo } from 'react';
+import { useCallback, useEffect, useMemo } from 'react';
 import { useSelector } from 'react-redux';
 import { getBioEntitiesFeatures } from './getBioEntitiesFeatures';
 import { getMarkersFeatures } from './getMarkersFeatures';
@@ -67,19 +67,18 @@ export const useOlMapPinsLayer = (): VectorLayer<VectorSource<Feature<Geometry>>
     ],
   );
 
-  const vectorSource = useMemo(() => {
-    return new VectorSource({
-      features: [...elementsFeatures],
-    });
-  }, [elementsFeatures]);
+  const vectorSource = useMemo(() => new VectorSource(), []);
 
-  const pinsLayer = useMemo(
+  useEffect(() => {
+    vectorSource.clear();
+    vectorSource.addFeatures(elementsFeatures);
+  }, [elementsFeatures, vectorSource]);
+
+  return useMemo(
     () =>
       new VectorLayer({
         source: vectorSource,
       }),
     [vectorSource],
   );
-
-  return pinsLayer;
 };
diff --git a/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index c564b662..a6350f63 100644
--- a/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -6,15 +6,15 @@ import { MarkerLine, Reaction } from '@/types/models';
 import { LinePoint } from '@/types/reactions';
 import { usePointToProjection } from '@/utils/map/usePointToProjection';
 import { Feature } from 'ol';
-import { SimpleGeometry } from 'ol/geom';
 import VectorLayer from 'ol/layer/Vector';
 import VectorSource from 'ol/source/Vector';
 import Fill from 'ol/style/Fill';
 import Stroke from 'ol/style/Stroke';
 import Style from 'ol/style/Style';
-import { useMemo } from 'react';
+import { useEffect, useMemo } from 'react';
 import { useSelector } from 'react-redux';
 import { createOverlayLineFeature } from '@/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayLineFeature';
+import { Geometry } from 'ol/geom';
 import { getLineFeature } from './getLineFeature';
 
 const getLinePoints = ({ start, end }: Pick<MarkerLine, 'start' | 'end'>): LinePoint => [
@@ -25,7 +25,7 @@ const getLinePoints = ({ start, end }: Pick<MarkerLine, 'start' | 'end'>): LineP
 const getReactionsLines = (reactions: Reaction[]): LinePoint[] =>
   reactions.map(({ lines }) => lines.map(getLinePoints)).flat();
 
-export const useOlMapReactionsLayer = (): VectorLayer<VectorSource<Feature<SimpleGeometry>>> => {
+export const useOlMapReactionsLayer = (): VectorLayer<VectorSource<Feature<Geometry>>> => {
   const pointToProjection = usePointToProjection();
   const reactions = useSelector(allReactionsSelectorOfCurrentMap);
   const markers = useSelector(markersLinesCurrentMapDataSelector);
@@ -47,13 +47,15 @@ export const useOlMapReactionsLayer = (): VectorLayer<VectorSource<Feature<Simpl
     [markers, pointToProjection],
   );
 
-  const vectorSource = useMemo(() => {
-    return new VectorSource({
-      features: [...reactionsLinesFeatures, ...markerLinesFeatures],
-    });
-  }, [reactionsLinesFeatures, markerLinesFeatures]);
+  const vectorSource = useMemo(() => new VectorSource(), []);
 
-  const reactionsLayer = useMemo(
+  useEffect(() => {
+    vectorSource.clear();
+    vectorSource.addFeatures(reactionsLinesFeatures);
+    vectorSource.addFeatures(markerLinesFeatures);
+  }, [reactionsLinesFeatures, markerLinesFeatures, vectorSource]);
+
+  return useMemo(
     () =>
       new VectorLayer({
         source: vectorSource,
@@ -64,6 +66,4 @@ export const useOlMapReactionsLayer = (): VectorLayer<VectorSource<Feature<Simpl
       }),
     [vectorSource],
   );
-
-  return reactionsLayer;
 };
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.ts b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.ts
index bfec74c6..ad70201b 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.ts
@@ -2,6 +2,7 @@
 import { useOlMapPinsLayer } from '@/components/Map/MapViewer/utils/config/pinsLayer/useOlMapPinsLayer';
 import { useOlMapReactionsLayer } from '@/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer';
 import { useOlMapCommentsLayer } from '@/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer';
+import { useMemo } from 'react';
 import { MapConfig } from '../../MapViewer.types';
 
 export const useOlMapCommonLayers = (): MapConfig['layers'] => {
@@ -9,5 +10,7 @@ export const useOlMapCommonLayers = (): MapConfig['layers'] => {
   const reactionsLayer = useOlMapReactionsLayer();
   const commentsLayer = useOlMapCommentsLayer();
 
-  return [pinsLayer, reactionsLayer, commentsLayer];
+  return useMemo(() => {
+    return [pinsLayer, reactionsLayer, commentsLayer];
+  }, [pinsLayer, reactionsLayer, commentsLayer]);
 };
diff --git a/src/utils/useDebouncedValue.test.ts b/src/utils/useDebouncedValue.test.ts
new file mode 100644
index 00000000..d0691ad2
--- /dev/null
+++ b/src/utils/useDebouncedValue.test.ts
@@ -0,0 +1,50 @@
+/* eslint-disable no-magic-numbers */
+import { act, renderHook } from '@testing-library/react';
+import useDebouncedValue from './useDebouncedValue';
+
+jest.useFakeTimers();
+
+describe('useDebouncedValue', () => {
+  it('should return the initial value immediately', () => {
+    const { result } = renderHook(() => useDebouncedValue('initial', 500));
+    expect(result.current).toBe('initial');
+  });
+
+  it('should update the value after the specified delay', () => {
+    const { result, rerender } = renderHook(({ value, delay }) => useDebouncedValue(value, delay), {
+      initialProps: { value: 'initial', delay: 500 },
+    });
+
+    rerender({ value: 'updated', delay: 500 });
+
+    expect(result.current).toBe('initial');
+
+    act(() => {
+      jest.advanceTimersByTime(500);
+    });
+
+    expect(result.current).toBe('updated');
+  });
+
+  it('should clear the timeout if value changes quickly', () => {
+    const { result, rerender } = renderHook(({ value, delay }) => useDebouncedValue(value, delay), {
+      initialProps: { value: 'initial', delay: 500 },
+    });
+
+    rerender({ value: 'intermediate', delay: 500 });
+
+    act(() => {
+      jest.advanceTimersByTime(300);
+    });
+
+    expect(result.current).toBe('initial');
+
+    rerender({ value: 'final', delay: 500 });
+
+    act(() => {
+      jest.advanceTimersByTime(500);
+    });
+
+    expect(result.current).toBe('final');
+  });
+});
diff --git a/src/utils/useDebouncedValue.ts b/src/utils/useDebouncedValue.ts
new file mode 100644
index 00000000..5d52da6b
--- /dev/null
+++ b/src/utils/useDebouncedValue.ts
@@ -0,0 +1,15 @@
+import { useEffect, useState } from 'react';
+
+export default function useDebouncedValue<T>(value: T, delay: number): T {
+  const [debouncedValue, setDebouncedValue] = useState(value);
+
+  useEffect(() => {
+    const handler = setTimeout(() => setDebouncedValue(value), delay);
+
+    return () => {
+      clearTimeout(handler);
+    };
+  }, [value, delay]);
+
+  return debouncedValue;
+}
-- 
GitLab


From 5b3ba912e7f657ea91dc318ef9c04614d364bc97 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Mon, 18 Nov 2024 11:56:08 +0100
Subject: [PATCH 18/29] feat(vector-map): assign testStyle and strokeStyle to
 const in styleFunction

---
 .../utils/shapes/elements/BaseMultiPolygon.ts | 23 +++++++++++--------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
index 1513dd63..b53e6ee6 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
@@ -197,11 +197,12 @@ export default abstract class BaseMultiPolygon {
     let text: string;
 
     this.styles.forEach(style => {
-      if (style.getGeometry() instanceof Polygon) {
-        type = (style.getGeometry() as Polygon).get('type');
-        text = (style.getGeometry() as Polygon).get('text');
-        fontSize = (style.getGeometry() as Polygon).get('fontSize');
-        lineWidth = (style.getGeometry() as Polygon).get('lineWidth');
+      const styleGeometry = style.getGeometry();
+      if (styleGeometry instanceof Polygon) {
+        type = styleGeometry.get('type');
+        text = styleGeometry.get('text');
+        fontSize = styleGeometry.get('fontSize');
+        lineWidth = styleGeometry.get('lineWidth');
       }
       if (
         [MAP_ELEMENT_TYPES.MODIFICATION, MAP_ELEMENT_TYPES.TEXT].includes(type) &&
@@ -211,15 +212,17 @@ export default abstract class BaseMultiPolygon {
       }
 
       const clonedStyle = style.clone();
-      if (type === 'text') {
-        clonedStyle.getText()?.setScale(textScale);
-        clonedStyle.getText()?.setText(text);
+      const textStyle = clonedStyle.getText();
+      const strokeStyle = clonedStyle.getStroke();
+      if (type === 'text' && textStyle) {
+        textStyle.setScale(textScale);
+        textStyle.setText(text);
       }
-      if (clonedStyle.getStroke() && lineWidth) {
+      if (strokeStyle && lineWidth) {
         if (lineWidth * textScale < 0.08 && this.sboTerm !== COMPLEX_SBO_TERM) {
           clonedStyle.setStroke(null);
         } else {
-          clonedStyle.getStroke()?.setWidth(lineWidth * textScale);
+          strokeStyle.setWidth(lineWidth * textScale);
         }
       }
       styles.push(clonedStyle);
-- 
GitLab


From 032c9eaafa04070cbcec658ab4402c2825671b93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 21 Nov 2024 15:05:00 +0100
Subject: [PATCH 19/29] feat(vector-map): implement semantic view

---
 .../reactionsLayer/processModelElements.ts    |  8 ++
 .../reactionsLayer/useOlMapReactionsLayer.ts  | 13 ++-
 .../utils/shapes/elements/BaseMultiPolygon.ts | 93 ++++++++++++++++++-
 .../utils/shapes/elements/Compartment.ts      | 22 +++++
 .../shapes/elements/CompartmentCircle.test.ts |  6 +-
 .../shapes/elements/CompartmentCircle.ts      | 10 ++
 .../elements/CompartmentPathway.test.ts       |  6 +-
 .../shapes/elements/CompartmentPathway.ts     | 20 +++-
 .../shapes/elements/CompartmentSquare.test.ts |  6 +-
 .../shapes/elements/CompartmentSquare.ts      | 10 ++
 .../utils/shapes/elements/MapElement.test.ts  |  4 +
 .../utils/shapes/elements/MapElement.ts       | 10 ++
 .../utils/shapes/reaction/Reaction.test.ts    |  2 +
 .../utils/shapes/reaction/Reaction.ts         | 23 +++++
 .../utils/shapes/text/getTextStyle.ts         |  4 +-
 15 files changed, 225 insertions(+), 12 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
index 066712d0..42914e0d 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
@@ -14,6 +14,7 @@ import { BioShapesDict, LineTypeDict } from '@/redux/shapes/shapes.types';
 import { OverlayOrder } from '@/redux/overlayBioEntity/overlayBioEntity.utils';
 import { OverlayBioEntityRender } from '@/types/OLrendering';
 import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
+import VectorSource from 'ol/source/Vector';
 
 export default function processModelElements(
   modelElements: ModelElements,
@@ -22,6 +23,7 @@ export default function processModelElements(
   groupedElementsOverlays: Record<string, Array<OverlayBioEntityRender>>,
   overlaysOrder: Array<OverlayOrder>,
   getOverlayColor: GetOverlayBioEntityColorByAvailableProperties,
+  vectorSource: VectorSource,
   mapInstance: MapInstance,
   pointToProjection: UsePointToProjectionResult,
 ): Array<MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph> {
@@ -47,6 +49,8 @@ export default function processModelElements(
     if (element.sboTerm === 'SBO:0000290') {
       const compartmentProps = {
         id: element.id,
+        complexId: element.complex,
+        compartmentId: element.compartment,
         x: element.x,
         y: element.y,
         nameX: element.nameX,
@@ -68,6 +72,7 @@ export default function processModelElements(
         fontSize: element.fontSize,
         pointToProjection,
         mapInstance,
+        vectorSource,
       };
       if (element.shape === 'OVAL_COMPARTMENT') {
         validElements.push(new CompartmentCircle(compartmentProps));
@@ -83,6 +88,8 @@ export default function processModelElements(
       validElements.push(
         new MapElement({
           id: element.id,
+          complexId: element.complex,
+          compartmentId: element.compartment,
           sboTerm: element.sboTerm,
           shapes: elementShapes,
           x: element.x,
@@ -107,6 +114,7 @@ export default function processModelElements(
           fontSize: element.fontSize,
           pointToProjection,
           mapInstance,
+          vectorSource,
           modifications: element.modificationResidues,
           lineTypes,
           bioShapes: shapes,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index 43fabc21..514cd60d 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -61,6 +61,8 @@ export const useOlMapReactionsLayer = ({
 
   const pointToProjection = usePointToProjection();
 
+  const vectorSource = useMemo(() => new VectorSource(), []);
+
   useEffect(() => {
     if (currentModelId) {
       dispatch(getModelElements(currentModelId));
@@ -130,11 +132,12 @@ export const useOlMapReactionsLayer = ({
         arrowTypes,
         shapes: reactionShapes,
         pointToProjection,
+        vectorSource,
         mapInstance,
       });
       return reactionObject.features;
     });
-  }, [arrowTypes, lineTypes, mapInstance, modelReactions, pointToProjection, shapes]);
+  }, [arrowTypes, lineTypes, mapInstance, modelReactions, pointToProjection, shapes, vectorSource]);
 
   const elements: Array<
     MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph
@@ -149,18 +152,20 @@ export const useOlMapReactionsLayer = ({
       groupedElementsOverlays,
       overlaysOrder,
       getOverlayBioEntityColorByAvailableProperties,
+      vectorSource,
       mapInstance,
       pointToProjection,
     );
   }, [
     modelElements,
     shapes,
-    pointToProjection,
-    mapInstance,
     lineTypes,
     groupedElementsOverlays,
     overlaysOrder,
     getOverlayBioEntityColorByAvailableProperties,
+    vectorSource,
+    mapInstance,
+    pointToProjection,
   ]);
 
   const features = useMemo(() => {
@@ -174,8 +179,6 @@ export const useOlMapReactionsLayer = ({
     ];
   }, [elements, linesOverlaysFeatures, markerOverlaysFeatures, reactions]);
 
-  const vectorSource = useMemo(() => new VectorSource(), []);
-
   useEffect(() => {
     vectorSource.clear();
     vectorSource.addFeatures(features);
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
index 5997a743..2c046ee6 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
@@ -2,7 +2,7 @@
 import Polygon from 'ol/geom/Polygon';
 import { Style } from 'ol/style';
 import Feature, { FeatureLike } from 'ol/Feature';
-import { MultiPolygon } from 'ol/geom';
+import { Geometry, MultiPolygon } from 'ol/geom';
 import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
 import {
   HorizontalAlign,
@@ -17,11 +17,14 @@ import {
   COMPLEX_SBO_TERM,
   MAP_ELEMENT_TYPES,
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
+import VectorSource from 'ol/source/Vector';
 
 export interface BaseMapElementProps {
   type: string;
   sboTerm?: string;
   id: number;
+  complexId?: number | null;
+  compartmentId: number | null;
   x: number;
   y: number;
   width: number;
@@ -39,6 +42,7 @@ export interface BaseMapElementProps {
   fillColor: Color;
   borderColor: Color;
   pointToProjection: UsePointToProjectionResult;
+  vectorSource: VectorSource;
 }
 
 export default abstract class BaseMultiPolygon {
@@ -48,6 +52,10 @@ export default abstract class BaseMultiPolygon {
 
   id: number;
 
+  complexId?: number | null;
+
+  compartmentId: number | null;
+
   x: number;
 
   y: number;
@@ -88,10 +96,14 @@ export default abstract class BaseMultiPolygon {
 
   pointToProjection: UsePointToProjectionResult;
 
+  vectorSource: VectorSource;
+
   constructor({
     type,
     sboTerm,
     id,
+    complexId,
+    compartmentId,
     x,
     y,
     width,
@@ -109,10 +121,13 @@ export default abstract class BaseMultiPolygon {
     fillColor,
     borderColor,
     pointToProjection,
+    vectorSource,
   }: BaseMapElementProps) {
     this.type = type;
     this.sboTerm = sboTerm;
     this.id = id;
+    this.complexId = complexId;
+    this.compartmentId = compartmentId;
     this.x = x;
     this.y = y;
     this.width = width;
@@ -130,6 +145,7 @@ export default abstract class BaseMultiPolygon {
     this.fillColor = fillColor;
     this.borderColor = borderColor;
     this.pointToProjection = pointToProjection;
+    this.vectorSource = vectorSource;
   }
 
   protected abstract createPolygons(): void;
@@ -176,25 +192,90 @@ export default abstract class BaseMultiPolygon {
         }
         return 1;
       },
+      getMapExtent: (resolution: number): [number, number, number, number] | undefined => {
+        const view = mapInstance?.getView();
+        const center = view?.getCenter();
+        const size = mapInstance?.getSize();
+
+        if (!size || !center) {
+          return undefined;
+        }
+        const extentWidth = size[0] * resolution;
+        const extentHeight = size[1] * resolution;
+
+        return [
+          center[0] - extentWidth / 2,
+          center[1] - extentHeight / 2,
+          center[0] + extentWidth / 2,
+          center[1] + extentHeight / 2,
+        ];
+      },
       id: this.id,
+      complexId: this.complexId,
+      compartmentId: this.compartmentId,
       type: this.type,
     });
-
+    this.feature.setId(this.id);
     this.feature.setStyle(this.getStyle.bind(this));
   }
 
   protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
     const styles: Array<Style> = [];
     const getScale = feature.get('getScale');
+    const getMapExtent = feature.get('getMapExtent');
     let scale = 1;
+    let cover = false;
+    let coverRation: number = 1;
     if (getScale instanceof Function) {
       scale = getScale(resolution);
     }
 
+    let hide = false;
+    if (getMapExtent instanceof Function && this.type === 'COMPARTMENT') {
+      const mapExtent = getMapExtent(resolution);
+      const featureExtent = feature.getGeometry()?.getExtent();
+      if (featureExtent && mapExtent) {
+        const mapArea =
+          Math.abs(mapExtent[2] - mapExtent[0]) * Math.abs(mapExtent[3] - mapExtent[1]);
+        const compartmentArea =
+          Math.abs(featureExtent[2] - featureExtent[0]) *
+          Math.abs(featureExtent[3] - featureExtent[1]);
+        coverRation = compartmentArea / mapArea;
+        if (coverRation < 0.05 && scale < 1) {
+          cover = true;
+        }
+        (feature as Feature).set('filled', cover);
+      }
+    }
+
+    let complex: Feature<Geometry> | null;
+    let compartment: Feature<Geometry> | null;
+    if (this.complexId) {
+      complex = this.vectorSource.getFeatureById(this.complexId);
+      if (complex) {
+        if (complex.get('hidden')) {
+          hide = true;
+        }
+      }
+    }
+    if (this.compartmentId) {
+      compartment = this.vectorSource.getFeatureById(this.compartmentId);
+      if (compartment) {
+        if (compartment.get('filled')) {
+          hide = true;
+        }
+      }
+    }
+    (feature as Feature).set('hidden', hide);
+    if (hide) {
+      return undefined;
+    }
+
     let type: string;
     let fontSize: number;
     let lineWidth: number;
     let text: string;
+    let coverStyle: Style | undefined;
 
     this.styles.forEach(style => {
       const styleGeometry = style.getGeometry();
@@ -203,6 +284,14 @@ export default abstract class BaseMultiPolygon {
         text = styleGeometry.get('text');
         fontSize = styleGeometry.get('fontSize');
         lineWidth = styleGeometry.get('lineWidth');
+        coverStyle = styleGeometry.get('coverStyle');
+      }
+      if (cover) {
+        if (coverStyle) {
+          coverStyle.setZIndex(this.zIndex + 1000);
+          styles.push(coverStyle);
+        }
+        return;
       }
       if (
         [MAP_ELEMENT_TYPES.MODIFICATION, MAP_ELEMENT_TYPES.TEXT].includes(type) &&
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
index 9c50b1bc..425ea167 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
@@ -14,9 +14,12 @@ import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/s
 import { MapInstance } from '@/types/map';
 import { Color } from '@/types/models';
 import { MAP_ELEMENT_TYPES } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
+import VectorSource from 'ol/source/Vector';
 
 export interface CompartmentProps {
   id: number;
+  complexId?: number | null;
+  compartmentId: number | null;
   x: number;
   y: number;
   width: number;
@@ -38,6 +41,7 @@ export interface CompartmentProps {
   borderColor: Color;
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
+  vectorSource: VectorSource;
 }
 
 export default abstract class Compartment extends BaseMultiPolygon {
@@ -53,6 +57,8 @@ export default abstract class Compartment extends BaseMultiPolygon {
 
   constructor({
     id,
+    complexId,
+    compartmentId,
     x,
     y,
     width,
@@ -74,10 +80,13 @@ export default abstract class Compartment extends BaseMultiPolygon {
     borderColor,
     pointToProjection,
     mapInstance,
+    vectorSource,
   }: CompartmentProps) {
     super({
       type: 'COMPARTMENT',
       id,
+      complexId,
+      compartmentId,
       x,
       y,
       width,
@@ -95,6 +104,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
       fillColor,
       borderColor,
       pointToProjection,
+      vectorSource,
     });
     this.outerWidth = outerWidth;
     this.innerWidth = innerWidth;
@@ -108,6 +118,18 @@ export default abstract class Compartment extends BaseMultiPolygon {
   protected abstract getCompartmentCoords(): void;
 
   protected createPolygons(): void {
+    const coverPolygon = new Polygon([this.outerCoords]);
+    const coverStyle = new Style({
+      geometry: coverPolygon,
+      fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 255 }) }),
+    });
+    coverPolygon.set('coverStyle', coverStyle);
+    this.styles.push(
+      new Style({
+        geometry: coverPolygon,
+      }),
+    );
+
     const framePolygon = new Polygon([this.outerCoords, this.innerCoords]);
     framePolygon.set('type', MAP_ELEMENT_TYPES.COMPARTMENT);
     this.styles.push(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
index 3a880e94..c39c7fc5 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
@@ -17,6 +17,7 @@ import CompartmentCircle, {
 } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle';
 import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords';
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
+import VectorSource from 'ol/source/Vector';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -26,7 +27,7 @@ jest.mock('../coords/getEllipseCoords');
 jest.mock('../style/getFill');
 jest.mock('../style/rgbToHex');
 
-describe('MapElement', () => {
+describe('CompartmentCircle', () => {
   let props: CompartmentCircleProps;
 
   beforeEach(() => {
@@ -41,6 +42,8 @@ describe('MapElement', () => {
     });
     props = {
       id: 1,
+      complexId: null,
+      compartmentId: null,
       x: 0,
       y: 0,
       width: 100,
@@ -62,6 +65,7 @@ describe('MapElement', () => {
       nameHorizontalAlign: 'CENTER',
       pointToProjection: jest.fn(),
       mapInstance,
+      vectorSource: new VectorSource(),
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
index ad8440b3..55576865 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
@@ -14,9 +14,12 @@ import {
 import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords';
 import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment';
 import { Color } from '@/types/models';
+import VectorSource from 'ol/source/Vector';
 
 export type CompartmentCircleProps = {
   id: number;
+  complexId?: number | null;
+  compartmentId: number | null;
   x: number;
   y: number;
   width: number;
@@ -38,11 +41,14 @@ export type CompartmentCircleProps = {
   nameHorizontalAlign?: HorizontalAlign;
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
+  vectorSource: VectorSource;
 };
 
 export default class CompartmentCircle extends Compartment {
   constructor({
     id,
+    complexId,
+    compartmentId,
     x,
     y,
     width,
@@ -64,9 +70,12 @@ export default class CompartmentCircle extends Compartment {
     nameHorizontalAlign = 'CENTER',
     pointToProjection,
     mapInstance,
+    vectorSource,
   }: CompartmentCircleProps) {
     super({
       id,
+      complexId,
+      compartmentId,
       x,
       y,
       width,
@@ -88,6 +97,7 @@ export default class CompartmentCircle extends Compartment {
       borderColor,
       pointToProjection,
       mapInstance,
+      vectorSource,
     });
   }
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
index db133e7f..96d27c33 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
@@ -17,6 +17,7 @@ import CompartmentPathway, {
 } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway';
 import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords';
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
+import VectorSource from 'ol/source/Vector';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -26,7 +27,7 @@ jest.mock('../coords/getEllipseCoords');
 jest.mock('../style/getFill');
 jest.mock('../style/rgbToHex');
 
-describe('MapElement', () => {
+describe('CompartmentPathway', () => {
   let props: CompartmentPathwayProps;
 
   beforeEach(() => {
@@ -41,6 +42,8 @@ describe('MapElement', () => {
     });
     props = {
       id: 1,
+      complexId: null,
+      compartmentId: null,
       x: 0,
       y: 0,
       width: 100,
@@ -60,6 +63,7 @@ describe('MapElement', () => {
       nameHorizontalAlign: 'CENTER',
       pointToProjection: jest.fn(() => [10, 10]),
       mapInstance,
+      vectorSource: new VectorSource(),
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
index bcc70aa1..768519fa 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
@@ -14,9 +14,15 @@ import Polygon from 'ol/geom/Polygon';
 import BaseMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon';
 import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle';
 import { Color } from '@/types/models';
+import VectorSource from 'ol/source/Vector';
+import { Style } from 'ol/style';
+import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
+import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
 
 export type CompartmentPathwayProps = {
   id: number;
+  complexId?: number | null;
+  compartmentId: number | null;
   x: number;
   y: number;
   width: number;
@@ -36,6 +42,7 @@ export type CompartmentPathwayProps = {
   nameHorizontalAlign?: HorizontalAlign;
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
+  vectorSource: VectorSource;
 };
 
 export default class CompartmentPathway extends BaseMultiPolygon {
@@ -43,6 +50,8 @@ export default class CompartmentPathway extends BaseMultiPolygon {
 
   constructor({
     id,
+    complexId,
+    compartmentId,
     x,
     y,
     width,
@@ -62,10 +71,13 @@ export default class CompartmentPathway extends BaseMultiPolygon {
     nameHorizontalAlign = 'CENTER',
     pointToProjection,
     mapInstance,
+    vectorSource,
   }: CompartmentPathwayProps) {
     super({
       type: 'COMPARTMENT',
       id,
+      complexId,
+      compartmentId,
       x,
       y,
       width,
@@ -83,6 +95,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
       fillColor,
       borderColor,
       pointToProjection,
+      vectorSource,
     });
     this.outerWidth = outerWidth;
     this.createPolygons();
@@ -97,11 +110,16 @@ export default class CompartmentPathway extends BaseMultiPolygon {
         this.pointToProjection({ x: this.x + this.width, y: this.y }),
         this.pointToProjection({ x: this.x + this.width, y: this.y + this.height }),
         this.pointToProjection({ x: this.x, y: this.y + this.height }),
+        this.pointToProjection({ x: this.x, y: this.y }),
       ],
     ]);
     compartmentPolygon.set('type', MAP_ELEMENT_TYPES.COMPARTMENT);
     compartmentPolygon.set('lineWidth', this.outerWidth);
-
+    const coverStyle = new Style({
+      geometry: compartmentPolygon,
+      fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 255 }) }),
+    });
+    compartmentPolygon.set('coverStyle', coverStyle);
     this.styles.push(
       getStyle({
         geometry: compartmentPolygon,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
index 45c5ad45..812eb5f1 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
@@ -16,6 +16,7 @@ import CompartmentSquare, {
 } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare';
 import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords';
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
+import VectorSource from 'ol/source/Vector';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -24,7 +25,7 @@ jest.mock('../coords/getPolygonCoords');
 jest.mock('../style/getFill');
 jest.mock('../style/rgbToHex');
 
-describe('MapElement', () => {
+describe('CompartmentSquare', () => {
   let props: CompartmentSquareProps;
 
   beforeEach(() => {
@@ -39,6 +40,8 @@ describe('MapElement', () => {
     });
     props = {
       id: 1,
+      complexId: null,
+      compartmentId: null,
       x: 0,
       y: 0,
       width: 100,
@@ -60,6 +63,7 @@ describe('MapElement', () => {
       nameHorizontalAlign: 'CENTER',
       pointToProjection: jest.fn(),
       mapInstance,
+      vectorSource: new VectorSource(),
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
index 1d71ac43..5b1598c8 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
@@ -13,9 +13,12 @@ import {
 import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords';
 import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment';
 import { Color } from '@/types/models';
+import VectorSource from 'ol/source/Vector';
 
 export type CompartmentSquareProps = {
   id: number;
+  complexId?: number | null;
+  compartmentId: number | null;
   x: number;
   y: number;
   width: number;
@@ -37,11 +40,14 @@ export type CompartmentSquareProps = {
   nameHorizontalAlign?: HorizontalAlign;
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
+  vectorSource: VectorSource;
 };
 
 export default class CompartmentSquare extends Compartment {
   constructor({
     id,
+    complexId,
+    compartmentId,
     x,
     y,
     width,
@@ -63,9 +69,12 @@ export default class CompartmentSquare extends Compartment {
     nameHorizontalAlign = 'CENTER',
     pointToProjection,
     mapInstance,
+    vectorSource,
   }: CompartmentSquareProps) {
     super({
       id,
+      complexId,
+      compartmentId,
       x,
       y,
       width,
@@ -87,6 +96,7 @@ export default class CompartmentSquare extends Compartment {
       borderColor,
       pointToProjection,
       mapInstance,
+      vectorSource,
     });
   }
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
index 81665cc6..ba9c4612 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
@@ -17,6 +17,7 @@ import {
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 import { shapesFixture } from '@/models/fixtures/shapesFixture';
+import VectorSource from 'ol/source/Vector';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -40,6 +41,8 @@ describe('MapElement', () => {
     });
     props = {
       id: 1,
+      complexId: null,
+      compartmentId: null,
       sboTerm: 'SBO:2313123',
       shapes: shapesFixture,
       x: 0,
@@ -61,6 +64,7 @@ describe('MapElement', () => {
       nameHorizontalAlign: 'CENTER',
       pointToProjection: jest.fn(),
       mapInstance,
+      vectorSource: new VectorSource(),
       getOverlayColor: (): string => '#ffffff',
     };
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
index 575d8d77..00295941 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
@@ -28,9 +28,12 @@ import { getPolygonLatitudeCoordinates } from '@/components/Map/MapViewer/utils/
 import { ZERO } from '@/constants/common';
 import { OverlayOrder } from '@/redux/overlayBioEntity/overlayBioEntity.utils';
 import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
+import VectorSource from 'ol/source/Vector';
 
 export type MapElementProps = {
   id: number;
+  complexId?: number | null;
+  compartmentId: number | null;
   sboTerm: string;
   shapes: Array<Shape>;
   x: number;
@@ -55,6 +58,7 @@ export type MapElementProps = {
   activity?: boolean;
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
+  vectorSource: VectorSource;
   bioShapes?: BioShapesDict;
   lineTypes?: LineTypeDict;
   modifications?: Array<Modification>;
@@ -90,6 +94,8 @@ export default class MapElement extends BaseMultiPolygon {
 
   constructor({
     id,
+    complexId,
+    compartmentId,
     sboTerm,
     shapes,
     x,
@@ -114,6 +120,7 @@ export default class MapElement extends BaseMultiPolygon {
     activity,
     pointToProjection,
     mapInstance,
+    vectorSource,
     bioShapes = {},
     lineTypes = {},
     modifications = [],
@@ -125,6 +132,8 @@ export default class MapElement extends BaseMultiPolygon {
       type: FEATURE_TYPE.ALIAS,
       sboTerm,
       id,
+      complexId,
+      compartmentId,
       x,
       y,
       width,
@@ -142,6 +151,7 @@ export default class MapElement extends BaseMultiPolygon {
       fillColor,
       borderColor,
       pointToProjection,
+      vectorSource,
     });
     this.shapes = shapes;
     this.lineWidth = lineWidth;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts
index 86025da4..70c5b3e4 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts
@@ -10,6 +10,7 @@ import { shapesFixture } from '@/models/fixtures/shapesFixture';
 import View from 'ol/View';
 import { ArrowTypeDict, LineTypeDict } from '@/redux/shapes/shapes.types';
 import { ArrowType, LineType } from '@/types/models';
+import VectorSource from 'ol/source/Vector';
 
 describe('Layer', () => {
   let props: ReactionProps;
@@ -42,6 +43,7 @@ describe('Layer', () => {
         acc[arrow.arrowType] = arrow.shapes;
         return acc;
       }, {}),
+      vectorSource: new VectorSource(),
       mapInstance,
     };
   });
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts
index f8140764..4a4a9a61 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts
@@ -20,6 +20,7 @@ import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shape
 import { ArrowTypeDict, LineTypeDict } from '@/redux/shapes/shapes.types';
 import { FEATURE_TYPE } from '@/constants/features';
 import getScaledElementStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle';
+import VectorSource from 'ol/source/Vector';
 
 export interface ReactionProps {
   id: number;
@@ -33,6 +34,7 @@ export interface ReactionProps {
   arrowTypes: ArrowTypeDict;
   shapes: Array<Shape>;
   pointToProjection: UsePointToProjectionResult;
+  vectorSource: VectorSource;
   mapInstance: MapInstance;
 }
 
@@ -59,6 +61,8 @@ export default class Reaction {
 
   pointToProjection: UsePointToProjectionResult;
 
+  vectorSource: VectorSource;
+
   mapInstance: MapInstance;
 
   features: Array<Feature> = [];
@@ -75,6 +79,7 @@ export default class Reaction {
     arrowTypes,
     shapes,
     pointToProjection,
+    vectorSource,
     mapInstance,
   }: ReactionProps) {
     this.id = id;
@@ -88,6 +93,7 @@ export default class Reaction {
     this.arrowTypes = arrowTypes;
     this.shapes = shapes;
     this.pointToProjection = pointToProjection;
+    this.vectorSource = vectorSource;
     this.mapInstance = mapInstance;
 
     this.drawReaction();
@@ -323,7 +329,24 @@ export default class Reaction {
     return circleFeature;
   }
 
+  protected isAnyOfElementsHidden(): boolean {
+    let isHidden = false;
+    [...this.products, ...this.reactants, ...this.modifiers].forEach(reactionElement => {
+      const feature = this.vectorSource.getFeatureById(reactionElement.element);
+      if (feature && feature.get('hidden')) {
+        isHidden = true;
+        return true;
+      }
+      return false;
+    });
+    return isHidden;
+  }
+
   protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
+    if (this.isAnyOfElementsHidden()) {
+      return undefined;
+    }
+
     const styles: Array<Style> = [];
     const maxZoom = this.mapInstance?.getView().get('originalMaxZoom');
     const minResolution = this.mapInstance?.getView().getResolutionForZoom(maxZoom);
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
index da9eed33..4858fb21 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
@@ -8,12 +8,14 @@ export default function getTextStyle({
   color,
   zIndex,
   horizontalAlign,
+  overflow = true,
 }: {
   text: string;
   fontSize: number;
   color: string;
   zIndex: number;
   horizontalAlign: HorizontalAlign;
+  overflow?: boolean;
 }): Style {
   return new Style({
     text: new Text({
@@ -25,7 +27,7 @@ export default function getTextStyle({
       placement: 'point',
       textAlign: horizontalAlign.toLowerCase() as CanvasTextAlign,
       textBaseline: 'middle',
-      overflow: true,
+      overflow,
     }),
     zIndex,
   });
-- 
GitLab


From c6c92cbb876339f04f9d6983c9f7b6df7145f95e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Fri, 22 Nov 2024 10:08:57 +0100
Subject: [PATCH 20/29] feat(vector-map): switching between background types

---
 .../MapNavigation.component.test.tsx          |  8 ++
 .../utils/useOverviewImageLinkActions.test.ts |  9 ++
 .../ElementLink.component.test.tsx            |  2 +
 .../AssociatedSubmap.component.test.tsx       |  3 +
 .../BioEntitiesSubmapItem.component.test.tsx  |  3 +
 .../PinsListItem.component.test.tsx           |  4 +
 .../SubmapsDrawer/SubmapsDrawer.test.tsx      |  3 +
 src/components/Map/Map.component.tsx          |  7 +-
 .../MapAdditionalActions.component.test.tsx   |  2 +
 .../utils/useAdditionalActions.test.ts        |  2 +
 .../MapVectorBackgroundSelector.component.tsx | 27 ++++++
 .../listeners/useOlMapVectorListeners.test.ts |  2 +
 .../reactionsLayer/processModelElements.ts    |  3 +
 .../reactionsLayer/useOlMapReactionsLayer.ts  |  5 ++
 .../utils/config/useOlMapVectorLayers.test.ts |  2 +
 .../config/useOlMapWhiteCardLayer.test.ts     |  2 +
 .../utils/shapes/elements/BaseMultiPolygon.ts | 70 ++++++++-------
 .../utils/shapes/elements/Compartment.ts      |  3 +
 .../shapes/elements/CompartmentCircle.test.ts |  2 +
 .../shapes/elements/CompartmentCircle.ts      |  3 +
 .../elements/CompartmentPathway.test.ts       |  2 +
 .../shapes/elements/CompartmentPathway.ts     |  3 +
 .../shapes/elements/CompartmentSquare.test.ts |  2 +
 .../shapes/elements/CompartmentSquare.ts      |  3 +
 .../utils/shapes/elements/MapElement.test.ts  |  2 +
 .../utils/shapes/elements/MapElement.ts       |  3 +
 .../utils/config/useOlMapCommonLayers.test.ts |  2 +
 .../utils/config/useOlMapLayers.test.ts       |  2 +
 .../utils/config/useOlMapTileLayer.test.ts    |  2 +
 .../utils/config/useOlMapView.test.ts         |  2 +
 .../utils/listeners/useOlMapListeners.test.ts |  2 +
 src/redux/map/map.constants.ts                |  7 ++
 src/redux/map/map.enums.ts                    |  7 ++
 src/redux/map/map.fixtures.ts                 |  3 +
 src/redux/map/map.reducers.ts                 | 12 ++-
 src/redux/map/map.selectors.ts                |  2 +
 src/redux/map/map.slice.ts                    |  3 +
 src/redux/map/map.types.ts                    |  4 +-
 .../checkIfIsMapUpdateActionValid.test.ts     |  2 +
 .../map/middleware/map.middleware.test.ts     |  2 +
 .../pluginsManager/map/data/getBounds.test.ts |  2 +
 .../map/fitBounds/fitBounds.test.ts           |  3 +
 .../pluginsManager/map/openMap.test.ts        |  4 +
 .../map/position/getCenter.test.ts            |  2 +
 .../map/triggerSearch/triggerSearch.test.ts   |  2 +
 .../pluginsManager/map/zoom/getZoom.test.ts   |  3 +
 .../pluginsManager/map/zoom/setZoom.test.ts   |  2 +
 src/shared/Select/Select.component.test.tsx   | 41 +++++++++
 src/shared/Select/Select.component.tsx        | 90 +++++++++++++++++++
 src/shared/Select/index.tsx                   |  1 +
 src/utils/map/useSetBounds.test.ts            |  3 +
 .../useReduxBusQueryManager.test.ts           |  2 +
 52 files changed, 349 insertions(+), 35 deletions(-)
 create mode 100644 src/components/Map/MapVectorBackgroundSelector/MapVectorBackgroundSelector.component.tsx
 create mode 100644 src/redux/map/map.enums.ts
 create mode 100644 src/shared/Select/Select.component.test.tsx
 create mode 100644 src/shared/Select/Select.component.tsx
 create mode 100644 src/shared/Select/index.tsx

diff --git a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx
index 2af7f3fb..35c89a0c 100644
--- a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx
+++ b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx
@@ -10,6 +10,7 @@ import {
 } from '@/utils/testing/getReduxWrapperWithStore';
 import { act, render, screen, within } from '@testing-library/react';
 import { HISTAMINE_MAP_ID, MAIN_MAP_ID } from '@/constants/mocks';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { MapNavigation } from './MapNavigation.component';
 
 const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
@@ -35,6 +36,7 @@ describe('MapNavigation - component', () => {
         loading: 'succeeded',
         error: { message: '', name: '' },
         openedMaps: openedMapsThreeSubmapsFixture,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
 
@@ -55,6 +57,7 @@ describe('MapNavigation - component', () => {
         loading: 'succeeded',
         error: { message: '', name: '' },
         openedMaps: openedMapsThreeSubmapsFixture,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
 
@@ -81,6 +84,7 @@ describe('MapNavigation - component', () => {
         loading: 'succeeded',
         error: { message: '', name: '' },
         openedMaps: openedMapsThreeSubmapsFixture,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
 
@@ -111,6 +115,7 @@ describe('MapNavigation - component', () => {
           modelId: HISTAMINE_MAP_ID,
         },
         openedMaps: openedMapsThreeSubmapsFixture,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
         loading: 'succeeded',
         error: { message: '', name: '' },
       },
@@ -157,6 +162,7 @@ describe('MapNavigation - component', () => {
             modelId: HISTAMINE_MAP_ID,
           },
           openedMaps: openedMapsThreeSubmapsFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
           loading: 'succeeded',
           error: { message: '', name: '' },
         },
@@ -183,6 +189,7 @@ describe('MapNavigation - component', () => {
             modelId: HISTAMINE_MAP_ID,
           },
           openedMaps: openedMapsThreeSubmapsFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
           loading: 'succeeded',
           error: { message: '', name: '' },
         },
@@ -207,6 +214,7 @@ describe('MapNavigation - component', () => {
             modelId: HISTAMINE_MAP_ID,
           },
           openedMaps: openedMapsThreeSubmapsFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
           loading: 'succeeded',
           error: { message: '', name: '' },
         },
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts
index afcf6cb1..2df46d7c 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts
@@ -17,6 +17,7 @@ import { OverviewImageLink } from '@/types/models';
 import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
 import { renderHook } from '@testing-library/react';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import {
   FIRST_ARRAY_ELEMENT,
   NOOP,
@@ -59,6 +60,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           loading: 'succeeded',
           error: { name: '', message: '' },
           openedMaps: openedMapsThreeSubmapsFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
       });
 
@@ -109,6 +111,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           loading: 'succeeded',
           error: { name: '', message: '' },
           openedMaps: openedMapsThreeSubmapsFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
       });
 
@@ -166,6 +169,7 @@ describe('useOverviewImageLinkActions - hook', () => {
             loading: 'succeeded',
             error: { name: '', message: '' },
             openedMaps: openedMapsThreeSubmapsFixture,
+            backgroundType: MapBackgroundsEnum.SEMANTIC,
           },
           models: {
             data: MODELS_MOCK_SHORT,
@@ -244,6 +248,7 @@ describe('useOverviewImageLinkActions - hook', () => {
             loading: 'succeeded',
             error: { name: '', message: '' },
             openedMaps: openedMapsInitialValueFixture,
+            backgroundType: MapBackgroundsEnum.SEMANTIC,
           },
           models: {
             data: MODELS_MOCK_SHORT,
@@ -348,6 +353,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           loading: 'succeeded',
           error: { name: '', message: '' },
           openedMaps: openedMapsInitialValueFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
         models: {
           data: MODELS_MOCK_SHORT,
@@ -405,6 +411,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           loading: 'succeeded',
           error: { name: '', message: '' },
           openedMaps: openedMapsInitialValueFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
         models: {
           data: MODELS_MOCK_SHORT,
@@ -466,6 +473,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           loading: 'succeeded',
           error: { name: '', message: '' },
           openedMaps: openedMapsInitialValueFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
         models: {
           data: MODELS_MOCK_SHORT,
@@ -529,6 +537,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           loading: 'succeeded',
           error: { name: '', message: '' },
           openedMaps: openedMapsInitialValueFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
         models: {
           data: MODELS_MOCK_SHORT,
diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.test.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.test.tsx
index bb30b23f..9ed58cb2 100644
--- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.test.tsx
+++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsTable/ElementsOnMapCell/ElementLink/ElementLink.component.test.tsx
@@ -14,6 +14,7 @@ import {
 import { render, screen, waitFor } from '@testing-library/react';
 import { HttpStatusCode } from 'axios';
 import { MockStoreEnhanced } from 'redux-mock-store';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { ElementLink } from './ElementLink.component';
 
 const mockedAxiosNewClient = mockNetworkNewAPIResponse();
@@ -208,6 +209,7 @@ describe('ElementLink - component', () => {
                 lastPosition: DEFAULT_POSITION,
               },
             ],
+            backgroundType: MapBackgroundsEnum.SEMANTIC,
           },
         },
       );
diff --git a/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/AssociatedSubmap.component.test.tsx b/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/AssociatedSubmap.component.test.tsx
index d3880a09..4cc91adc 100644
--- a/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/AssociatedSubmap.component.test.tsx
+++ b/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/AssociatedSubmap.component.test.tsx
@@ -18,6 +18,7 @@ import {
   getReduxWrapperWithStore,
 } from '@/utils/testing/getReduxWrapperWithStore';
 import { act, render, screen } from '@testing-library/react';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { AssociatedSubmap } from './AssociatedSubmap.component';
 
 const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
@@ -91,6 +92,7 @@ describe('AssociatedSubmap - component', () => {
           loading: 'succeeded',
           error: { name: '', message: '' },
           openedMaps: openedMapsInitialValueFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
         bioEntity: {
           ...BIOENTITY_INITIAL_STATE_MOCK,
@@ -150,6 +152,7 @@ describe('AssociatedSubmap - component', () => {
           loading: 'succeeded',
           error: { name: '', message: '' },
           openedMaps: openedMapsThreeSubmapsFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
         bioEntity: {
           ...BIOENTITY_INITIAL_STATE_MOCK,
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesSubmapItem/BioEntitiesSubmapItem.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesSubmapItem/BioEntitiesSubmapItem.component.test.tsx
index aa753d71..0cc8a551 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesSubmapItem/BioEntitiesSubmapItem.component.test.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesSubmapItem/BioEntitiesSubmapItem.component.test.tsx
@@ -13,6 +13,7 @@ import {
   openedMapsInitialValueFixture,
   openedMapsThreeSubmapsFixture,
 } from '@/redux/map/map.fixtures';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { BioEntitiesSubmapItem } from './BioEntitiesSubmapItem.component';
 
 const CORE_MAP_ID = 5053;
@@ -100,6 +101,7 @@ describe('BioEntitiesSubmapItem - component', () => {
         loading: 'succeeded',
         error: { name: '', message: '' },
         openedMaps: openedMapsInitialValueFixture,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
 
@@ -157,6 +159,7 @@ describe('BioEntitiesSubmapItem - component', () => {
         loading: 'succeeded',
         error: { name: '', message: '' },
         openedMaps: openedMapsThreeSubmapsFixture,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
 
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.test.tsx
index b54bea43..9cfb68d8 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.test.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.test.tsx
@@ -11,6 +11,7 @@ import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/ma
 import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock';
 import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import { MockStoreEnhanced } from 'redux-mock-store';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { PinTypeWithNone } from '../PinsList.types';
 import { PinsListItem } from './PinsListItem.component';
 
@@ -37,6 +38,7 @@ const INITIAL_STORE_STATE: InitialStoreState = {
     loading: 'succeeded',
     error: { message: '', name: '' },
     openedMaps: openedMapsThreeSubmapsFixture,
+    backgroundType: MapBackgroundsEnum.SEMANTIC,
   },
 };
 
@@ -169,6 +171,7 @@ describe('PinsListItem - component ', () => {
           loading: 'succeeded',
           error: { message: '', name: '' },
           openedMaps: openedMapsThreeSubmapsFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
       },
     );
@@ -202,6 +205,7 @@ describe('PinsListItem - component ', () => {
           loading: 'succeeded',
           error: { message: '', name: '' },
           openedMaps: openedMapsThreeSubmapsFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
       },
     );
diff --git a/src/components/Map/Drawer/SubmapsDrawer/SubmapsDrawer.test.tsx b/src/components/Map/Drawer/SubmapsDrawer/SubmapsDrawer.test.tsx
index 42754f4e..825bccfd 100644
--- a/src/components/Map/Drawer/SubmapsDrawer/SubmapsDrawer.test.tsx
+++ b/src/components/Map/Drawer/SubmapsDrawer/SubmapsDrawer.test.tsx
@@ -11,6 +11,7 @@ import {
   openedMapsInitialValueFixture,
   openedMapsThreeSubmapsFixture,
 } from '@/redux/map/map.fixtures';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { SubmapsDrawer } from './SubmapsDrawer';
 
 const MAIN_MAP_ID = 5053;
@@ -64,6 +65,7 @@ describe('SubmapsDrawer - component', () => {
         loading: 'succeeded',
         error: { name: '', message: '' },
         openedMaps: openedMapsInitialValueFixture,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
 
@@ -109,6 +111,7 @@ describe('SubmapsDrawer - component', () => {
         openedMaps: openedMapsThreeSubmapsFixture,
         loading: 'succeeded',
         error: { name: '', message: '' },
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
 
diff --git a/src/components/Map/Map.component.tsx b/src/components/Map/Map.component.tsx
index 67b7187c..d0927b3a 100644
--- a/src/components/Map/Map.component.tsx
+++ b/src/components/Map/Map.component.tsx
@@ -3,18 +3,23 @@ import { Drawer } from '@/components/Map/Drawer';
 import { Legend } from '@/components/Map/Legend';
 import { MapViewer } from '@/components/Map/MapViewer';
 import { MapLoader } from '@/components/Map/MapLoader/MapLoader.component';
+import { MapVectorBackgroundSelector } from '@/components/Map/MapVectorBackgroundSelector/MapVectorBackgroundSelector.component';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { vectorRenderingSelector } from '@/redux/models/models.selectors';
 import { MapAdditionalActions } from './MapAdditionalActions';
 import { MapAdditionalOptions } from './MapAdditionalOptions';
 import { PluginsDrawer } from './PluginsDrawer';
 
 export const Map = (): JSX.Element => {
+  const vectorRendering = useAppSelector(vectorRenderingSelector);
   return (
     <div
       className="relative z-0 h-screen w-full overflow-hidden bg-black"
       data-testid="map-container"
     >
       <MapViewer />
-      <MapAdditionalOptions />
+      {!vectorRendering && <MapAdditionalOptions />}
+      {vectorRendering && <MapVectorBackgroundSelector />}
       <Drawer />
       <PluginsDrawer />
       <Legend />
diff --git a/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.test.tsx b/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.test.tsx
index 06bd09fe..5fe7353f 100644
--- a/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.test.tsx
+++ b/src/components/Map/MapAdditionalActions/MapAdditionalActions.component.test.tsx
@@ -11,6 +11,7 @@ import {
 import { act, render, screen } from '@testing-library/react';
 import Map from 'ol/Map';
 import { MockStoreEnhanced } from 'redux-mock-store';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { MapAdditionalActions } from './MapAdditionalActions.component';
 import { useVisibleBioEntitiesPolygonCoordinates } from './utils/useVisibleBioEntitiesPolygonCoordinates';
 
@@ -145,6 +146,7 @@ describe('MapAdditionalActions - component', () => {
             message: '',
           },
           openedMaps: [],
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
       });
 
diff --git a/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts b/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts
index 898b10e0..8e9ca7df 100644
--- a/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts
+++ b/src/components/Map/MapAdditionalActions/utils/useAdditionalActions.test.ts
@@ -8,6 +8,7 @@ import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreA
 import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
 import { renderHook } from '@testing-library/react';
 import Map from 'ol/Map';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { useAddtionalActions } from './useAdditionalActions';
 import { useVisibleBioEntitiesPolygonCoordinates } from './useVisibleBioEntitiesPolygonCoordinates';
 
@@ -94,6 +95,7 @@ describe('useAddtionalActions - hook', () => {
                 message: '',
               },
               openedMaps: [],
+              backgroundType: MapBackgroundsEnum.SEMANTIC,
             },
           },
           {
diff --git a/src/components/Map/MapVectorBackgroundSelector/MapVectorBackgroundSelector.component.tsx b/src/components/Map/MapVectorBackgroundSelector/MapVectorBackgroundSelector.component.tsx
new file mode 100644
index 00000000..32af5af4
--- /dev/null
+++ b/src/components/Map/MapVectorBackgroundSelector/MapVectorBackgroundSelector.component.tsx
@@ -0,0 +1,27 @@
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { mapBackgroundTypeSelector } from '@/redux/map/map.selectors';
+import { twMerge } from 'tailwind-merge';
+import { MAP_BACKGROUND_TYPES } from '@/redux/map/map.constants';
+import { setMapBackgroundType } from '@/redux/map/map.slice';
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { Select } from '@/shared/Select';
+
+export const MapVectorBackgroundSelector = (): JSX.Element => {
+  const dispatch = useAppDispatch();
+  const backgroundType = useAppSelector(mapBackgroundTypeSelector);
+
+  const handleChange = (selectedBackgroundType: number): void => {
+    dispatch(setMapBackgroundType(selectedBackgroundType));
+  };
+
+  return (
+    <div className={twMerge('absolute right-6 top-[calc(64px+40px+24px)] z-10 flex')}>
+      <Select
+        options={MAP_BACKGROUND_TYPES}
+        selectedId={backgroundType}
+        onChange={handleChange}
+        width={100}
+      />
+    </div>
+  );
+};
diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.test.ts
index 8b772615..2821fe91 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.test.ts
@@ -5,6 +5,7 @@ import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithSto
 import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
 import { useOlMapVectorListeners } from '@/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners';
 import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 
 jest.mock('./mouseClick/mouseLeftClick/onMapLeftClick', () => ({
   __esModule: true,
@@ -25,6 +26,7 @@ describe('useOlMapVectorListeners - util', () => {
       loading: 'succeeded',
       error: { message: '', name: '' },
       openedMaps: openedMapsThreeSubmapsFixture,
+      backgroundType: MapBackgroundsEnum.SEMANTIC,
     },
   });
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
index 42914e0d..357350e5 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
@@ -26,6 +26,7 @@ export default function processModelElements(
   vectorSource: VectorSource,
   mapInstance: MapInstance,
   pointToProjection: UsePointToProjectionResult,
+  mapBackgroundType: number,
 ): Array<MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph> {
   const validElements: Array<
     MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph
@@ -73,6 +74,7 @@ export default function processModelElements(
         pointToProjection,
         mapInstance,
         vectorSource,
+        mapBackgroundType,
       };
       if (element.shape === 'OVAL_COMPARTMENT') {
         validElements.push(new CompartmentCircle(compartmentProps));
@@ -121,6 +123,7 @@ export default function processModelElements(
           overlays: groupedElementsOverlays[element.id],
           overlaysOrder,
           getOverlayColor,
+          mapBackgroundType,
         }),
       );
     }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index 514cd60d..a6c6253e 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -38,6 +38,7 @@ import { parseSurfaceMarkersToBioEntityRender } from '@/components/Map/MapViewer
 import MarkerOverlay from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay';
 import processModelElements from '@/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements';
 import useDebouncedValue from '@/utils/useDebouncedValue';
+import { mapBackgroundTypeSelector } from '@/redux/map/map.selectors';
 
 export const useOlMapReactionsLayer = ({
   mapInstance,
@@ -53,10 +54,12 @@ export const useOlMapReactionsLayer = ({
   const lineTypes = useSelector(lineTypesSelector);
   const arrowTypes = useSelector(arrowTypesSelector);
   const overlaysOrder = useSelector(getOverlayOrderSelector);
+  const mapBackgroundType = useSelector(mapBackgroundTypeSelector);
   const currentMarkers = useAppSelector(markersSufraceOfCurrentMapDataSelector);
   const markersRender = parseSurfaceMarkersToBioEntityRender(currentMarkers);
   const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
   const debouncedBioEntities = useDebouncedValue(bioEntities, 2000);
+
   const { getOverlayBioEntityColorByAvailableProperties } = useGetOverlayColor();
 
   const pointToProjection = usePointToProjection();
@@ -155,6 +158,7 @@ export const useOlMapReactionsLayer = ({
       vectorSource,
       mapInstance,
       pointToProjection,
+      mapBackgroundType,
     );
   }, [
     modelElements,
@@ -166,6 +170,7 @@ export const useOlMapReactionsLayer = ({
     vectorSource,
     mapInstance,
     pointToProjection,
+    mapBackgroundType,
   ]);
 
   const features = useMemo(() => {
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.test.ts
index eee20645..fed682be 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.test.ts
@@ -7,6 +7,7 @@ import VectorLayer from 'ol/layer/Vector';
 import React from 'react';
 import { useOlMap } from '@/components/Map/MapViewer/utils/useOlMap';
 import { useOlMapVectorLayers } from '@/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 
 const useRefValue = {
   current: null,
@@ -59,6 +60,7 @@ describe('useOlMapLayers - util', () => {
           message: '',
         },
         openedMaps: OPENED_MAPS_INITIAL_STATE,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
     const dummyElement = document.createElement('div');
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer.test.ts
index 2ee7f699..394abc0c 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer.test.ts
@@ -5,6 +5,7 @@ import { renderHook } from '@testing-library/react';
 import BaseLayer from 'ol/layer/Base';
 import VectorLayer from 'ol/layer/Vector';
 import React from 'react';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { useOlMapWhiteCardLayer } from './useOlMapWhiteCardLayer';
 
 const useRefValue = {
@@ -58,6 +59,7 @@ describe('useOlMapWhiteCardLayer - util', () => {
           message: '',
         },
         openedMaps: OPENED_MAPS_INITIAL_STATE,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
index 2c046ee6..a28643fb 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
@@ -18,6 +18,7 @@ import {
   MAP_ELEMENT_TYPES,
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import VectorSource from 'ol/source/Vector';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 
 export interface BaseMapElementProps {
   type: string;
@@ -43,6 +44,7 @@ export interface BaseMapElementProps {
   borderColor: Color;
   pointToProjection: UsePointToProjectionResult;
   vectorSource: VectorSource;
+  mapBackgroundType: number;
 }
 
 export default abstract class BaseMultiPolygon {
@@ -98,6 +100,8 @@ export default abstract class BaseMultiPolygon {
 
   vectorSource: VectorSource;
 
+  mapBackgroundType: number;
+
   constructor({
     type,
     sboTerm,
@@ -122,6 +126,7 @@ export default abstract class BaseMultiPolygon {
     borderColor,
     pointToProjection,
     vectorSource,
+    mapBackgroundType,
   }: BaseMapElementProps) {
     this.type = type;
     this.sboTerm = sboTerm;
@@ -146,6 +151,7 @@ export default abstract class BaseMultiPolygon {
     this.borderColor = borderColor;
     this.pointToProjection = pointToProjection;
     this.vectorSource = vectorSource;
+    this.mapBackgroundType = mapBackgroundType;
   }
 
   protected abstract createPolygons(): void;
@@ -231,44 +237,46 @@ export default abstract class BaseMultiPolygon {
     }
 
     let hide = false;
-    if (getMapExtent instanceof Function && this.type === 'COMPARTMENT') {
-      const mapExtent = getMapExtent(resolution);
-      const featureExtent = feature.getGeometry()?.getExtent();
-      if (featureExtent && mapExtent) {
-        const mapArea =
-          Math.abs(mapExtent[2] - mapExtent[0]) * Math.abs(mapExtent[3] - mapExtent[1]);
-        const compartmentArea =
-          Math.abs(featureExtent[2] - featureExtent[0]) *
-          Math.abs(featureExtent[3] - featureExtent[1]);
-        coverRation = compartmentArea / mapArea;
-        if (coverRation < 0.05 && scale < 1) {
-          cover = true;
+    if (this.mapBackgroundType === MapBackgroundsEnum.SEMANTIC) {
+      if (getMapExtent instanceof Function && this.type === 'COMPARTMENT') {
+        const mapExtent = getMapExtent(resolution);
+        const featureExtent = feature.getGeometry()?.getExtent();
+        if (featureExtent && mapExtent) {
+          const mapArea =
+            Math.abs(mapExtent[2] - mapExtent[0]) * Math.abs(mapExtent[3] - mapExtent[1]);
+          const compartmentArea =
+            Math.abs(featureExtent[2] - featureExtent[0]) *
+            Math.abs(featureExtent[3] - featureExtent[1]);
+          coverRation = compartmentArea / mapArea;
+          if (coverRation < 0.05 && scale < 1) {
+            cover = true;
+          }
+          (feature as Feature).set('filled', cover);
         }
-        (feature as Feature).set('filled', cover);
       }
-    }
 
-    let complex: Feature<Geometry> | null;
-    let compartment: Feature<Geometry> | null;
-    if (this.complexId) {
-      complex = this.vectorSource.getFeatureById(this.complexId);
-      if (complex) {
-        if (complex.get('hidden')) {
-          hide = true;
+      let complex: Feature<Geometry> | null;
+      let compartment: Feature<Geometry> | null;
+      if (this.complexId) {
+        complex = this.vectorSource.getFeatureById(this.complexId);
+        if (complex) {
+          if (complex.get('hidden')) {
+            hide = true;
+          }
         }
       }
-    }
-    if (this.compartmentId) {
-      compartment = this.vectorSource.getFeatureById(this.compartmentId);
-      if (compartment) {
-        if (compartment.get('filled')) {
-          hide = true;
+      if (this.compartmentId) {
+        compartment = this.vectorSource.getFeatureById(this.compartmentId);
+        if (compartment) {
+          if (compartment.get('filled')) {
+            hide = true;
+          }
         }
       }
-    }
-    (feature as Feature).set('hidden', hide);
-    if (hide) {
-      return undefined;
+      (feature as Feature).set('hidden', hide);
+      if (hide) {
+        return undefined;
+      }
     }
 
     let type: string;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
index 425ea167..c61b16d1 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
@@ -42,6 +42,7 @@ export interface CompartmentProps {
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
   vectorSource: VectorSource;
+  mapBackgroundType: number;
 }
 
 export default abstract class Compartment extends BaseMultiPolygon {
@@ -81,6 +82,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
     pointToProjection,
     mapInstance,
     vectorSource,
+    mapBackgroundType,
   }: CompartmentProps) {
     super({
       type: 'COMPARTMENT',
@@ -105,6 +107,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
       borderColor,
       pointToProjection,
       vectorSource,
+      mapBackgroundType,
     });
     this.outerWidth = outerWidth;
     this.innerWidth = innerWidth;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
index c39c7fc5..ba61e067 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
@@ -18,6 +18,7 @@ import CompartmentCircle, {
 import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords';
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 import VectorSource from 'ol/source/Vector';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -66,6 +67,7 @@ describe('CompartmentCircle', () => {
       pointToProjection: jest.fn(),
       mapInstance,
       vectorSource: new VectorSource(),
+      mapBackgroundType: MapBackgroundsEnum.SEMANTIC,
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
index 55576865..040cc8ee 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
@@ -42,6 +42,7 @@ export type CompartmentCircleProps = {
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
   vectorSource: VectorSource;
+  mapBackgroundType: number;
 };
 
 export default class CompartmentCircle extends Compartment {
@@ -71,6 +72,7 @@ export default class CompartmentCircle extends Compartment {
     pointToProjection,
     mapInstance,
     vectorSource,
+    mapBackgroundType,
   }: CompartmentCircleProps) {
     super({
       id,
@@ -98,6 +100,7 @@ export default class CompartmentCircle extends Compartment {
       pointToProjection,
       mapInstance,
       vectorSource,
+      mapBackgroundType,
     });
   }
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
index 96d27c33..26bf658b 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
@@ -18,6 +18,7 @@ import CompartmentPathway, {
 import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords';
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 import VectorSource from 'ol/source/Vector';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -64,6 +65,7 @@ describe('CompartmentPathway', () => {
       pointToProjection: jest.fn(() => [10, 10]),
       mapInstance,
       vectorSource: new VectorSource(),
+      mapBackgroundType: MapBackgroundsEnum.SEMANTIC,
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
index 768519fa..0fc13627 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
@@ -43,6 +43,7 @@ export type CompartmentPathwayProps = {
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
   vectorSource: VectorSource;
+  mapBackgroundType: number;
 };
 
 export default class CompartmentPathway extends BaseMultiPolygon {
@@ -72,6 +73,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
     pointToProjection,
     mapInstance,
     vectorSource,
+    mapBackgroundType,
   }: CompartmentPathwayProps) {
     super({
       type: 'COMPARTMENT',
@@ -96,6 +98,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
       borderColor,
       pointToProjection,
       vectorSource,
+      mapBackgroundType,
     });
     this.outerWidth = outerWidth;
     this.createPolygons();
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
index 812eb5f1..ae4148d2 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
@@ -17,6 +17,7 @@ import CompartmentSquare, {
 import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords';
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 import VectorSource from 'ol/source/Vector';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -64,6 +65,7 @@ describe('CompartmentSquare', () => {
       pointToProjection: jest.fn(),
       mapInstance,
       vectorSource: new VectorSource(),
+      mapBackgroundType: MapBackgroundsEnum.SEMANTIC,
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
index 5b1598c8..1732c177 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
@@ -41,6 +41,7 @@ export type CompartmentSquareProps = {
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
   vectorSource: VectorSource;
+  mapBackgroundType: number;
 };
 
 export default class CompartmentSquare extends Compartment {
@@ -70,6 +71,7 @@ export default class CompartmentSquare extends Compartment {
     pointToProjection,
     mapInstance,
     vectorSource,
+    mapBackgroundType,
   }: CompartmentSquareProps) {
     super({
       id,
@@ -97,6 +99,7 @@ export default class CompartmentSquare extends Compartment {
       pointToProjection,
       mapInstance,
       vectorSource,
+      mapBackgroundType,
     });
   }
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
index ba9c4612..1611b7fe 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
@@ -18,6 +18,7 @@ import {
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 import { shapesFixture } from '@/models/fixtures/shapesFixture';
 import VectorSource from 'ol/source/Vector';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -66,6 +67,7 @@ describe('MapElement', () => {
       mapInstance,
       vectorSource: new VectorSource(),
       getOverlayColor: (): string => '#ffffff',
+      mapBackgroundType: MapBackgroundsEnum.SEMANTIC,
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
index 00295941..4a0cd82c 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
@@ -65,6 +65,7 @@ export type MapElementProps = {
   overlays?: Array<OverlayBioEntityRender>;
   overlaysOrder?: Array<OverlayOrder>;
   getOverlayColor: GetOverlayBioEntityColorByAvailableProperties;
+  mapBackgroundType: number;
 };
 
 export default class MapElement extends BaseMultiPolygon {
@@ -127,6 +128,7 @@ export default class MapElement extends BaseMultiPolygon {
     overlays = [],
     overlaysOrder = [],
     getOverlayColor,
+    mapBackgroundType,
   }: MapElementProps) {
     super({
       type: FEATURE_TYPE.ALIAS,
@@ -152,6 +154,7 @@ export default class MapElement extends BaseMultiPolygon {
       borderColor,
       pointToProjection,
       vectorSource,
+      mapBackgroundType,
     });
     this.shapes = shapes;
     this.lineWidth = lineWidth;
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.test.ts
index 852f8093..b3117b1f 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.test.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.test.ts
@@ -6,6 +6,7 @@ import BaseLayer from 'ol/layer/Base';
 import React from 'react';
 import VectorLayer from 'ol/layer/Vector';
 import { useOlMapCommonLayers } from '@/components/Map/MapViewer/utils/config/useOlMapCommonLayers';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 
 const useRefValue = {
   current: null,
@@ -58,6 +59,7 @@ describe('useOlMapCommonLayers - util', () => {
           message: '',
         },
         openedMaps: OPENED_MAPS_INITIAL_STATE,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
 
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts
index ffcd6326..b4bfec60 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts
@@ -6,6 +6,7 @@ import BaseLayer from 'ol/layer/Base';
 import TileLayer from 'ol/layer/Tile';
 import React from 'react';
 import VectorLayer from 'ol/layer/Vector';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { useOlMapLayers } from './useOlMapLayers';
 
 const useRefValue = {
@@ -59,6 +60,7 @@ describe('useOlMapLayers - util', () => {
           message: '',
         },
         openedMaps: OPENED_MAPS_INITIAL_STATE,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
 
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.test.ts
index 8c5321cb..58e4aaa5 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.test.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapTileLayer.test.ts
@@ -5,6 +5,7 @@ import { renderHook } from '@testing-library/react';
 import BaseLayer from 'ol/layer/Base';
 import TileLayer from 'ol/layer/Tile';
 import React from 'react';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { useOlMapTileLayer } from './useOlMapTileLayer';
 
 const useRefValue = {
@@ -58,6 +59,7 @@ describe('useOlMapTileLayer - util', () => {
           message: '',
         },
         openedMaps: OPENED_MAPS_INITIAL_STATE,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
     });
 
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts
index 1b52b84c..d630b12b 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts
@@ -11,6 +11,7 @@ import { act, renderHook, waitFor } from '@testing-library/react';
 import { View } from 'ol';
 import Map from 'ol/Map';
 import React from 'react';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { useOlMap } from '../useOlMap';
 import { useOlMapView } from './useOlMapView';
 
@@ -95,6 +96,7 @@ describe('useOlMapView - util', () => {
           message: '',
         },
         openedMaps: OPENED_MAPS_INITIAL_STATE,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
       backgrounds: BACKGROUND_INITIAL_STATE_MOCK,
     });
diff --git a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts
index 340b61de..94a53559 100644
--- a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts
@@ -3,6 +3,7 @@ import { renderHook } from '@testing-library/react';
 import { View } from 'ol';
 import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
 import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import * as singleClickListener from './mapSingleClick/onMapSingleClick';
 import * as positionListener from './onMapPositionChange';
 import { useOlMapListeners } from './useOlMapListeners';
@@ -31,6 +32,7 @@ describe('useOlMapListeners - util', () => {
       loading: 'succeeded',
       error: { message: '', name: '' },
       openedMaps: openedMapsThreeSubmapsFixture,
+      backgroundType: MapBackgroundsEnum.SEMANTIC,
     },
   });
 
diff --git a/src/redux/map/map.constants.ts b/src/redux/map/map.constants.ts
index 3b9e9e53..90648de6 100644
--- a/src/redux/map/map.constants.ts
+++ b/src/redux/map/map.constants.ts
@@ -6,10 +6,16 @@ import {
   DEFAULT_TILE_SIZE,
 } from '@/constants/map';
 import { Point } from '@/types/map';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { MapData, MapState, OppenedMap } from './map.types';
 
 export const MAIN_MAP = 'Main map';
 
+export const MAP_BACKGROUND_TYPES = [
+  { id: MapBackgroundsEnum.NETWORK, name: 'Network' },
+  { id: MapBackgroundsEnum.SEMANTIC, name: 'Semantic' },
+];
+
 export const MODEL_ID_DEFAULT: number = 0;
 
 export const BACKGROUND_ID_DEFAULT: number = 0;
@@ -67,6 +73,7 @@ export const MAP_INITIAL_STATE: MapState = {
   loading: 'idle',
   error: { name: '', message: '' },
   openedMaps: OPENED_MAPS_INITIAL_STATE,
+  backgroundType: MapBackgroundsEnum.NETWORK,
 };
 
 export const INIT_MAP_SIZE_MODEL_ID_ERROR_PREFIX = 'Failed to initialize map size and model ID';
diff --git a/src/redux/map/map.enums.ts b/src/redux/map/map.enums.ts
new file mode 100644
index 00000000..8483ca33
--- /dev/null
+++ b/src/redux/map/map.enums.ts
@@ -0,0 +1,7 @@
+/* eslint-disable no-magic-numbers */
+enum MapBackgroundsEnum {
+  NETWORK = 1,
+  SEMANTIC = 2,
+}
+
+export default MapBackgroundsEnum;
diff --git a/src/redux/map/map.fixtures.ts b/src/redux/map/map.fixtures.ts
index 049bdc64..268b7f9d 100644
--- a/src/redux/map/map.fixtures.ts
+++ b/src/redux/map/map.fixtures.ts
@@ -1,5 +1,6 @@
 import { DEFAULT_ERROR } from '@/constants/errors';
 import { MODEL_ID_DEFAULT } from '@/redux/map/map.constants';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { MapData, MapState, OppenedMap } from './map.types';
 
 export const openedMapsInitialValueFixture: OppenedMap[] = [
@@ -54,6 +55,7 @@ export const initialMapStateFixture: MapState = {
   loading: 'idle',
   error: DEFAULT_ERROR,
   openedMaps: openedMapsInitialValueFixture,
+  backgroundType: MapBackgroundsEnum.SEMANTIC,
 };
 
 export const mapStateWithCurrentlySelectedMainMapFixture: MapState = {
@@ -71,4 +73,5 @@ export const mapStateWithCurrentlySelectedMainMapFixture: MapState = {
   loading: 'idle',
   error: DEFAULT_ERROR,
   openedMaps: openedMapsInitialValueFixture,
+  backgroundType: MapBackgroundsEnum.SEMANTIC,
 };
diff --git a/src/redux/map/map.reducers.ts b/src/redux/map/map.reducers.ts
index f193e753..5ae6707d 100644
--- a/src/redux/map/map.reducers.ts
+++ b/src/redux/map/map.reducers.ts
@@ -1,7 +1,7 @@
 import { DEFAULT_ZOOM } from '@/constants/map';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
-import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
-import { getPointMerged } from '../../utils/object/getPointMerged';
+import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit';
+import { getPointMerged } from '@/utils/object/getPointMerged';
 import {
   initMapBackground,
   initMapPosition,
@@ -221,3 +221,11 @@ export const initOpenedMapsReducer = (builder: ActionReducerMapBuilder<MapState>
     state.openedMaps = action.payload;
   });
 };
+
+export const setMapBackgroundTypeReducer = (
+  state: MapState,
+  action: PayloadAction<number>,
+): void => {
+  const { payload } = action;
+  state.backgroundType = payload;
+};
diff --git a/src/redux/map/map.selectors.ts b/src/redux/map/map.selectors.ts
index 257f99fd..8cb88035 100644
--- a/src/redux/map/map.selectors.ts
+++ b/src/redux/map/map.selectors.ts
@@ -34,3 +34,5 @@ export const mapDataLastZoomValue = createSelector(
 );
 
 export const mapDataMaxZoomValue = createSelector(mapDataSizeSelector, model => model.maxZoom);
+
+export const mapBackgroundTypeSelector = createSelector(mapSelector, map => map.backgroundType);
diff --git a/src/redux/map/map.slice.ts b/src/redux/map/map.slice.ts
index 3106a118..dbfd8bbe 100644
--- a/src/redux/map/map.slice.ts
+++ b/src/redux/map/map.slice.ts
@@ -12,6 +12,7 @@ import {
   setActiveMapReducer,
   setLastPositionZoomReducer,
   setMapBackgroundReducer,
+  setMapBackgroundTypeReducer,
   setMapDataReducer,
   setMapPositionReducer,
   updateLastClickReducer,
@@ -35,6 +36,7 @@ const mapSlice = createSlice({
     setLastPositionZoom: setLastPositionZoomReducer,
     updateLastClick: updateLastClickReducer,
     updateLastRightClick: updateLastRightClickReducer,
+    setMapBackgroundType: setMapBackgroundTypeReducer,
   },
   extraReducers: builder => {
     initMapPositionReducers(builder);
@@ -57,6 +59,7 @@ export const {
   setLastPositionZoom,
   updateLastClick,
   updateLastRightClick,
+  setMapBackgroundType,
 } = mapSlice.actions;
 
 export default mapSlice.reducer;
diff --git a/src/redux/map/map.types.ts b/src/redux/map/map.types.ts
index 72b600dc..dc838aaa 100644
--- a/src/redux/map/map.types.ts
+++ b/src/redux/map/map.types.ts
@@ -44,7 +44,9 @@ export type MapData = {
   };
 };
 
-export type MapState = FetchDataState<MapData, MapData> & { openedMaps: OppenedMap[] };
+export type MapState = FetchDataState<MapData, MapData> & { openedMaps: OppenedMap[] } & {
+  backgroundType: number;
+};
 
 export type SetMapDataActionPayload =
   | (Omit<Partial<MapData>, 'position' | 'projectId'> & {
diff --git a/src/redux/map/middleware/checkIfIsMapUpdateActionValid.test.ts b/src/redux/map/middleware/checkIfIsMapUpdateActionValid.test.ts
index 5580c024..1509a089 100644
--- a/src/redux/map/middleware/checkIfIsMapUpdateActionValid.test.ts
+++ b/src/redux/map/middleware/checkIfIsMapUpdateActionValid.test.ts
@@ -1,5 +1,6 @@
 import { RootState } from '@/redux/store';
 import { Loading } from '@/types/loadingState';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import {
   MAP_DATA_INITIAL_STATE,
   MIDDLEWARE_ALLOWED_ACTIONS,
@@ -17,6 +18,7 @@ const state: Pick<RootState, 'map'> = {
     loading: 'idle' as Loading,
     error: { name: '', message: '' },
     openedMaps: OPENED_MAPS_INITIAL_STATE,
+    backgroundType: MapBackgroundsEnum.SEMANTIC,
   },
 };
 
diff --git a/src/redux/map/middleware/map.middleware.test.ts b/src/redux/map/middleware/map.middleware.test.ts
index 11015648..835c5e2d 100644
--- a/src/redux/map/middleware/map.middleware.test.ts
+++ b/src/redux/map/middleware/map.middleware.test.ts
@@ -3,6 +3,7 @@ import { modelsFixture } from '@/models/fixtures/modelsFixture';
 import { Loading } from '@/types/loadingState';
 import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
 import { Action } from '@reduxjs/toolkit';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import {
   MAP_DATA_INITIAL_STATE,
   MIDDLEWARE_ALLOWED_ACTIONS,
@@ -63,6 +64,7 @@ const { store } = getReduxWrapperWithStore({
       modelId: modelsFixture[0].idObject,
     },
     openedMaps: OPENED_MAPS_INITIAL_STATE,
+    backgroundType: MapBackgroundsEnum.SEMANTIC,
   },
   models: {
     ...defaultSliceState,
diff --git a/src/services/pluginsManager/map/data/getBounds.test.ts b/src/services/pluginsManager/map/data/getBounds.test.ts
index 99f587da..b824df10 100644
--- a/src/services/pluginsManager/map/data/getBounds.test.ts
+++ b/src/services/pluginsManager/map/data/getBounds.test.ts
@@ -2,6 +2,7 @@
 import { MAP_DATA_INITIAL_STATE } from '@/redux/map/map.constants';
 import { store } from '@/redux/store';
 import { Map } from 'ol';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { MapManager } from '../mapManager';
 import { getBounds } from './getBounds';
 
@@ -45,6 +46,7 @@ describe('getBounds', () => {
               message: '',
             },
             openedMaps: [],
+            backgroundType: MapBackgroundsEnum.SEMANTIC,
           },
           // eslint-disable-next-line @typescript-eslint/no-explicit-any
         }) as any,
diff --git a/src/services/pluginsManager/map/fitBounds/fitBounds.test.ts b/src/services/pluginsManager/map/fitBounds/fitBounds.test.ts
index 1fd5ec32..de852078 100644
--- a/src/services/pluginsManager/map/fitBounds/fitBounds.test.ts
+++ b/src/services/pluginsManager/map/fitBounds/fitBounds.test.ts
@@ -2,6 +2,7 @@
 import { MAP_DATA_INITIAL_STATE } from '@/redux/map/map.constants';
 import { Map } from 'ol';
 import { store } from '@/redux/store';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { fitBounds } from './fitBounds';
 import { MapManager } from '../mapManager';
 
@@ -51,6 +52,7 @@ describe('fitBounds', () => {
                 message: '',
               },
               openedMaps: [],
+              backgroundType: MapBackgroundsEnum.SEMANTIC,
             },
             // eslint-disable-next-line @typescript-eslint/no-explicit-any
           }) as any,
@@ -101,6 +103,7 @@ describe('fitBounds', () => {
                 message: '',
               },
               openedMaps: [],
+              backgroundType: MapBackgroundsEnum.SEMANTIC,
             },
             // eslint-disable-next-line @typescript-eslint/no-explicit-any
           }) as any,
diff --git a/src/services/pluginsManager/map/openMap.test.ts b/src/services/pluginsManager/map/openMap.test.ts
index 152f81b5..b3d01c8f 100644
--- a/src/services/pluginsManager/map/openMap.test.ts
+++ b/src/services/pluginsManager/map/openMap.test.ts
@@ -3,6 +3,7 @@ import { openMapAndSetActive, setActiveMap } from '@/redux/map/map.slice';
 import { RootState, store } from '@/redux/store';
 import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
 import { MODELS_MOCK, MODELS_MOCK_SHORT } from '@/models/mocks/modelsMock';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { PluginsEventBus } from '../pluginsEventBus';
 import { openMap } from './openMap';
 
@@ -25,6 +26,7 @@ describe('openMap', () => {
             loading: 'succeeded',
             error: { message: '', name: '' },
             openedMaps: openedMapsThreeSubmapsFixture,
+            backgroundType: MapBackgroundsEnum.SEMANTIC,
           },
           models: {
             data: MODELS_MOCK_SHORT,
@@ -50,6 +52,7 @@ describe('openMap', () => {
             loading: 'succeeded',
             error: { message: '', name: '' },
             openedMaps: openedMapsThreeSubmapsFixture,
+            backgroundType: MapBackgroundsEnum.SEMANTIC,
           },
           models: {
             data: MODELS_MOCK,
@@ -77,6 +80,7 @@ describe('openMap', () => {
             loading: 'succeeded',
             error: { message: '', name: '' },
             openedMaps: openedMapsThreeSubmapsFixture,
+            backgroundType: MapBackgroundsEnum.SEMANTIC,
           },
           models: {
             data: MODELS_MOCK,
diff --git a/src/services/pluginsManager/map/position/getCenter.test.ts b/src/services/pluginsManager/map/position/getCenter.test.ts
index f9d9dd54..fe23d527 100644
--- a/src/services/pluginsManager/map/position/getCenter.test.ts
+++ b/src/services/pluginsManager/map/position/getCenter.test.ts
@@ -1,5 +1,6 @@
 import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
 import { RootState, store } from '@/redux/store';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { getCenter } from './getCenter';
 
 jest.mock('../../../../redux/store');
@@ -25,6 +26,7 @@ describe('getCenter - plugin method', () => {
           loading: 'succeeded',
           error: { message: '', name: '' },
           openedMaps: openedMapsThreeSubmapsFixture,
+          backgroundType: MapBackgroundsEnum.SEMANTIC,
         },
       }) as RootState,
   );
diff --git a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
index abb470aa..293f9476 100644
--- a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
+++ b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
@@ -11,6 +11,7 @@ import { RootState, store } from '@/redux/store';
 import { mockNetworkNewAPIResponse, mockNetworkResponse } from '@/utils/mockNetworkResponse';
 import { waitFor } from '@testing-library/react';
 import { HttpStatusCode } from 'axios';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { triggerSearch } from './triggerSearch';
 import { ERROR_INVALID_MODEL_ID_TYPE } from '../../errorMessages';
 
@@ -29,6 +30,7 @@ const MOCK_STATE = {
     loading: 'succeeded',
     error: { message: '', name: '' },
     openedMaps: openedMapsThreeSubmapsFixture,
+    backgroundType: MapBackgroundsEnum.SEMANTIC,
   },
   configuration: CONFIGURATION_INITIAL_STORE_MOCKS,
 };
diff --git a/src/services/pluginsManager/map/zoom/getZoom.test.ts b/src/services/pluginsManager/map/zoom/getZoom.test.ts
index b7808d53..669d8773 100644
--- a/src/services/pluginsManager/map/zoom/getZoom.test.ts
+++ b/src/services/pluginsManager/map/zoom/getZoom.test.ts
@@ -1,6 +1,7 @@
 /* eslint-disable no-magic-numbers */
 import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
 import { RootState, store } from '@/redux/store';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { getZoom } from './getZoom';
 
 jest.mock('../../../../redux/store');
@@ -28,6 +29,7 @@ describe('getZoom - plugin method', () => {
               loading: 'succeeded',
               error: { message: '', name: '' },
               openedMaps: openedMapsThreeSubmapsFixture,
+              backgroundType: MapBackgroundsEnum.SEMANTIC,
             },
           }) as RootState,
       );
@@ -57,6 +59,7 @@ describe('getZoom - plugin method', () => {
               loading: 'succeeded',
               error: { message: '', name: '' },
               openedMaps: openedMapsThreeSubmapsFixture,
+              backgroundType: MapBackgroundsEnum.SEMANTIC,
             },
           }) as RootState,
       );
diff --git a/src/services/pluginsManager/map/zoom/setZoom.test.ts b/src/services/pluginsManager/map/zoom/setZoom.test.ts
index 55502f30..2d5f8a3d 100644
--- a/src/services/pluginsManager/map/zoom/setZoom.test.ts
+++ b/src/services/pluginsManager/map/zoom/setZoom.test.ts
@@ -4,6 +4,7 @@ import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/ma
 import { setLastPositionZoom } from '@/redux/map/map.slice';
 import { RootState, store } from '@/redux/store';
 import { ZodError } from 'zod';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { setZoom } from './setZoom';
 
 jest.mock('../../../../redux/store');
@@ -36,6 +37,7 @@ describe('setZoom - plugin method', () => {
             loading: 'succeeded',
             error: { message: '', name: '' },
             openedMaps: openedMapsThreeSubmapsFixture,
+            backgroundType: MapBackgroundsEnum.SEMANTIC,
           },
         }) as RootState,
     );
diff --git a/src/shared/Select/Select.component.test.tsx b/src/shared/Select/Select.component.test.tsx
new file mode 100644
index 00000000..d7db8199
--- /dev/null
+++ b/src/shared/Select/Select.component.test.tsx
@@ -0,0 +1,41 @@
+/* eslint-disable no-magic-numbers */
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { Select } from '.';
+
+describe('Select Component', () => {
+  const mockOptions = [
+    { id: 1, name: 'Option 1' },
+    { id: 2, name: 'Option 2' },
+    { id: 3, name: 'Option 3' },
+  ];
+
+  const mockOnChange = jest.fn();
+
+  it('renders the Select component', () => {
+    render(<Select options={mockOptions} selectedId={1} onChange={mockOnChange} />);
+    expect(screen.getByTestId('select-component')).toBeInTheDocument();
+  });
+
+  it('displays the selected option name', () => {
+    render(<Select options={mockOptions} selectedId={1} onChange={mockOnChange} />);
+    expect(screen.getByText('Option 1')).toBeInTheDocument();
+  });
+
+  it('opens the dropdown when clicked', () => {
+    render(<Select options={mockOptions} selectedId={1} onChange={mockOnChange} />);
+    const toggleButton = screen.getByTestId('dropdown-button-name');
+
+    fireEvent.click(toggleButton);
+    expect(screen.getByRole('listbox')).toBeVisible();
+  });
+
+  it('calls onChange with the correct value when an option is clicked', () => {
+    render(<Select options={mockOptions} selectedId={1} onChange={mockOnChange} />);
+    fireEvent.click(screen.getByTestId('dropdown-button-name'));
+    const optionToSelect = screen.getByText('Option 3');
+
+    fireEvent.click(optionToSelect);
+    expect(mockOnChange).toHaveBeenCalledWith(3);
+  });
+});
diff --git a/src/shared/Select/Select.component.tsx b/src/shared/Select/Select.component.tsx
new file mode 100644
index 00000000..aa3ab6d5
--- /dev/null
+++ b/src/shared/Select/Select.component.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { twMerge } from 'tailwind-merge';
+import { useSelect } from 'downshift';
+import { Icon } from '@/shared/Icon';
+
+type SelectProps = {
+  options: Array<{ id: number; name: string }>;
+  selectedId: number;
+  onChange: (selectedId: number) => void;
+  width?: string | number;
+};
+
+export const Select = ({
+  options,
+  selectedId,
+  onChange,
+  width = '100%',
+}: SelectProps): React.JSX.Element => {
+  const selectedOption = options.find(option => option.id === selectedId);
+
+  const {
+    isOpen,
+    highlightedIndex,
+    getToggleButtonProps,
+    getMenuProps,
+    getItemProps,
+    selectedItem,
+  } = useSelect({
+    items: options,
+    selectedItem: selectedOption,
+    onSelectedItemChange: ({ selectedItem: newSelectedItem }) => {
+      if (newSelectedItem) {
+        onChange(newSelectedItem.id);
+      }
+    },
+    itemToString: item => (item ? item.name : ''),
+  });
+
+  const widthStyle = typeof width === 'number' ? { width: `${width}px` } : { width };
+
+  return (
+    <div
+      data-testid="select-component"
+      className={twMerge(
+        'relative rounded-t bg-white text-xs shadow-primary',
+        !isOpen && 'rounded-b',
+      )}
+      style={widthStyle}
+    >
+      <div
+        className={twMerge(
+          'flex cursor-pointer flex-row items-center justify-between rounded-t p-2',
+        )}
+        {...getToggleButtonProps()}
+      >
+        <span data-testid="dropdown-button-name" className="font-medium">
+          {selectedItem ? selectedItem.name : 'Select an option'}
+        </span>
+        <Icon
+          name="chevron-down"
+          className={twMerge('arrow-button h-6 w-6 fill-primary-500', isOpen && 'rotate-180')}
+        />
+      </div>
+      <ul
+        className={twMerge(
+          'absolute z-10 overflow-auto rounded-b bg-white shadow-lg',
+          !isOpen && 'hidden',
+        )}
+        style={widthStyle}
+        {...getMenuProps()}
+      >
+        {isOpen &&
+          options.map((item, index) => (
+            <li
+              className={twMerge(
+                'border-t',
+                highlightedIndex === index && 'text-primary-500',
+                selectedItem?.id === item.id && 'font-bold',
+                'flex flex-col p-2 shadow-sm',
+              )}
+              key={item.id}
+              {...getItemProps({ item, index })}
+            >
+              <span>{item.name}</span>
+            </li>
+          ))}
+      </ul>
+    </div>
+  );
+};
diff --git a/src/shared/Select/index.tsx b/src/shared/Select/index.tsx
new file mode 100644
index 00000000..3be3db6b
--- /dev/null
+++ b/src/shared/Select/index.tsx
@@ -0,0 +1 @@
+export { Select } from './Select.component';
diff --git a/src/utils/map/useSetBounds.test.ts b/src/utils/map/useSetBounds.test.ts
index 71ee1260..fde34efd 100644
--- a/src/utils/map/useSetBounds.test.ts
+++ b/src/utils/map/useSetBounds.test.ts
@@ -4,6 +4,7 @@ import { MAP_DATA_INITIAL_STATE } from '@/redux/map/map.constants';
 import { renderHook } from '@testing-library/react';
 import { Map } from 'ol';
 import { Coordinate } from 'ol/coordinate';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { getReduxWrapperWithStore } from '../testing/getReduxWrapperWithStore';
 import { useSetBounds } from './useSetBounds';
 
@@ -34,6 +35,7 @@ describe('useSetBounds - hook', () => {
               message: '',
             },
             openedMaps: [],
+            backgroundType: MapBackgroundsEnum.SEMANTIC,
           },
         },
         {
@@ -79,6 +81,7 @@ describe('useSetBounds - hook', () => {
               message: '',
             },
             openedMaps: [],
+            backgroundType: MapBackgroundsEnum.SEMANTIC,
           },
         },
         {
diff --git a/src/utils/query-manager/useReduxBusQueryManager.test.ts b/src/utils/query-manager/useReduxBusQueryManager.test.ts
index adecf259..e71069d6 100644
--- a/src/utils/query-manager/useReduxBusQueryManager.test.ts
+++ b/src/utils/query-manager/useReduxBusQueryManager.test.ts
@@ -2,6 +2,7 @@ import { MAP_DATA_INITIAL_STATE, OPENED_MAPS_INITIAL_STATE } from '@/redux/map/m
 import { Loading } from '@/types/loadingState';
 import { renderHook, waitFor } from '@testing-library/react';
 import mockRouter from 'next-router-mock';
+import MapBackgroundsEnum from '@/redux/map/map.enums';
 import { getReduxWrapperWithStore } from '../testing/getReduxWrapperWithStore';
 import { useReduxBusQueryManager } from './useReduxBusQueryManager';
 
@@ -70,6 +71,7 @@ describe('useReduxBusQueryManager - util', () => {
           },
         },
         openedMaps: OPENED_MAPS_INITIAL_STATE,
+        backgroundType: MapBackgroundsEnum.SEMANTIC,
       },
       backgrounds: loadedDataMock,
       models: loadedDataMock,
-- 
GitLab


From 749885943c387b595136cb301cf0b83e77eafabc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Mon, 25 Nov 2024 14:49:46 +0100
Subject: [PATCH 21/29] feat(vector-map): add text for filled compartments in a
 semantic view

---
 .../reactionsLayer/processModelElements.ts    |   4 +
 .../reactionsLayer/useOlMapReactionsLayer.ts  |   5 +-
 .../shapes/coords/findLargestExtent.test.ts   |  16 +++
 .../utils/shapes/coords/findLargestExtent.ts  |  16 +++
 .../shapes/coords/getDividedExtents.test.ts   |  74 ++++++++++++
 .../utils/shapes/coords/getDividedExtents.ts  |  51 ++++++++
 .../utils/shapes/elements/BaseMultiPolygon.ts |  84 ++++++-------
 .../utils/shapes/elements/Compartment.ts      |   4 +
 .../shapes/elements/CompartmentCircle.test.ts |   8 ++
 .../shapes/elements/CompartmentCircle.ts      |   4 +
 .../elements/CompartmentPathway.test.ts       |   8 ++
 .../shapes/elements/CompartmentPathway.ts     |   4 +
 .../shapes/elements/CompartmentSquare.test.ts |   8 ++
 .../shapes/elements/CompartmentSquare.ts      |   4 +
 .../utils/shapes/elements/MapElement.test.ts  |   8 ++
 .../utils/shapes/elements/MapElement.ts       |   4 +
 .../elements/handleSemanticView.test.ts       | 112 ++++++++++++++++++
 .../shapes/elements/handleSemanticView.ts     |  68 +++++++++++
 .../utils/shapes/style/getCoverStyles.test.ts |  88 ++++++++++++++
 .../utils/shapes/style/getCoverStyles.ts      |  50 ++++++++
 .../utils/shapes/text/getTextStyle.ts         |   2 +-
 .../text/getWrappedTextWithFontSize.test.ts   |  49 ++++++++
 .../shapes/text/getWrappedTextWithFontSize.ts |  59 +++++++++
 23 files changed, 686 insertions(+), 44 deletions(-)
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/findLargestExtent.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/findLargestExtent.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getDividedExtents.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getDividedExtents.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getWrappedTextWithFontSize.test.ts
 create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getWrappedTextWithFontSize.ts

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
index 357350e5..2063c53c 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
@@ -15,6 +15,7 @@ import { OverlayOrder } from '@/redux/overlayBioEntity/overlayBioEntity.utils';
 import { OverlayBioEntityRender } from '@/types/OLrendering';
 import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
 import VectorSource from 'ol/source/Vector';
+import { MapSize } from '@/redux/map/map.types';
 
 export default function processModelElements(
   modelElements: ModelElements,
@@ -27,6 +28,7 @@ export default function processModelElements(
   mapInstance: MapInstance,
   pointToProjection: UsePointToProjectionResult,
   mapBackgroundType: number,
+  mapSize: MapSize,
 ): Array<MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph> {
   const validElements: Array<
     MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph
@@ -75,6 +77,7 @@ export default function processModelElements(
         mapInstance,
         vectorSource,
         mapBackgroundType,
+        mapSize,
       };
       if (element.shape === 'OVAL_COMPARTMENT') {
         validElements.push(new CompartmentCircle(compartmentProps));
@@ -124,6 +127,7 @@ export default function processModelElements(
           overlaysOrder,
           getOverlayColor,
           mapBackgroundType,
+          mapSize,
         }),
       );
     }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index a6c6253e..4e289297 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -38,7 +38,7 @@ import { parseSurfaceMarkersToBioEntityRender } from '@/components/Map/MapViewer
 import MarkerOverlay from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/MarkerOverlay';
 import processModelElements from '@/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements';
 import useDebouncedValue from '@/utils/useDebouncedValue';
-import { mapBackgroundTypeSelector } from '@/redux/map/map.selectors';
+import { mapBackgroundTypeSelector, mapDataSizeSelector } from '@/redux/map/map.selectors';
 
 export const useOlMapReactionsLayer = ({
   mapInstance,
@@ -51,6 +51,7 @@ export const useOlMapReactionsLayer = ({
   const modelElements = useSelector(modelElementsSelector);
   const modelReactions = useSelector(newReactionsDataSelector);
   const shapes = useSelector(bioShapesSelector);
+  const mapSize = useSelector(mapDataSizeSelector);
   const lineTypes = useSelector(lineTypesSelector);
   const arrowTypes = useSelector(arrowTypesSelector);
   const overlaysOrder = useSelector(getOverlayOrderSelector);
@@ -159,6 +160,7 @@ export const useOlMapReactionsLayer = ({
       mapInstance,
       pointToProjection,
       mapBackgroundType,
+      mapSize,
     );
   }, [
     modelElements,
@@ -171,6 +173,7 @@ export const useOlMapReactionsLayer = ({
     mapInstance,
     pointToProjection,
     mapBackgroundType,
+    mapSize,
   ]);
 
   const features = useMemo(() => {
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/findLargestExtent.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/findLargestExtent.test.ts
new file mode 100644
index 00000000..2a41ebad
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/findLargestExtent.test.ts
@@ -0,0 +1,16 @@
+/* eslint-disable no-magic-numbers */
+
+import findLargestExtent from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/findLargestExtent';
+import { Extent } from 'ol/extent';
+
+describe('findLargestExtent', () => {
+  it('should find largest extent from a given array', () => {
+    const extents: Array<Extent> = [
+      [100, 100, 200, 200],
+      [150, 200, 400, 500],
+    ];
+
+    const result = findLargestExtent(extents);
+    expect(result).toEqual(extents[1]);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/findLargestExtent.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/findLargestExtent.ts
new file mode 100644
index 00000000..f013e03c
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/findLargestExtent.ts
@@ -0,0 +1,16 @@
+import { Extent, getArea } from 'ol/extent';
+
+export default function findLargestExtent(extents: Extent[]): Extent | null {
+  let largestExtent = null;
+  let maxArea = 0;
+
+  extents.forEach(extent => {
+    const area = getArea(extent);
+    if (area > maxArea) {
+      maxArea = area;
+      largestExtent = extent;
+    }
+  });
+
+  return largestExtent;
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getDividedExtents.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getDividedExtents.test.ts
new file mode 100644
index 00000000..ea4a9f89
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getDividedExtents.test.ts
@@ -0,0 +1,74 @@
+/* eslint-disable no-magic-numbers */
+import { Extent } from 'ol/extent';
+import getDividedExtents from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getDividedExtents';
+
+describe('getDividedExtents', () => {
+  it('should return original extents if there is no intersection with dividingExtent', () => {
+    const extentsArray: Array<Extent> = [
+      [0, 0, 10, 10],
+      [20, 20, 30, 30],
+    ];
+    const dividingExtent: Extent = [15, 15, 18, 18];
+
+    const result = getDividedExtents(extentsArray, dividingExtent);
+
+    expect(result).toEqual(extentsArray);
+  });
+
+  it('should divide extent when it intersects with dividingExtent', () => {
+    const extentsArray: Array<Extent> = [[0, 0, 20, 20]];
+    const dividingExtent: Extent = [10, 10, 15, 15];
+
+    const result = getDividedExtents(extentsArray, dividingExtent);
+
+    const expected = [
+      [0, 0, 10, 20],
+      [15, 0, 20, 20],
+      [0, 15, 20, 20],
+      [0, 0, 20, 10],
+    ];
+
+    expect(result).toEqual(expected);
+  });
+
+  it('should return a mix of original and divided extents when some extents intersect and others do not', () => {
+    const extentsArray: Array<Extent> = [
+      [0, 0, 20, 20],
+      [25, 25, 30, 30],
+    ];
+    const dividingExtent: Extent = [10, 10, 15, 15];
+
+    const result = getDividedExtents(extentsArray, dividingExtent);
+
+    const expected = [
+      [0, 0, 10, 20],
+      [15, 0, 20, 20],
+      [0, 15, 20, 20],
+      [0, 0, 20, 10],
+      [25, 25, 30, 30],
+    ];
+
+    expect(result).toEqual(expected);
+  });
+
+  it('should handle case where dividingExtent completely overlaps an extent', () => {
+    const extentsArray: Array<Extent> = [[10, 10, 20, 20]];
+    const dividingExtent: Extent = [10, 10, 20, 20];
+
+    const result = getDividedExtents(extentsArray, dividingExtent);
+
+    expect(result).toEqual([]);
+  });
+
+  it('should handle case where extents are completely outside dividingExtent', () => {
+    const extentsArray: Array<Extent> = [
+      [0, 0, 5, 5],
+      [25, 25, 30, 30],
+    ];
+    const dividingExtent: Extent = [10, 10, 20, 20];
+
+    const result = getDividedExtents(extentsArray, dividingExtent);
+
+    expect(result).toEqual(extentsArray);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getDividedExtents.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getDividedExtents.ts
new file mode 100644
index 00000000..09b36084
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getDividedExtents.ts
@@ -0,0 +1,51 @@
+/* eslint-disable no-magic-numbers */
+import { Extent } from 'ol/extent';
+
+export default function getDividedExtents(
+  extentsArray: Array<Extent>,
+  dividingExtent: Extent,
+): Array<Extent> {
+  let minX1: number;
+  let minY1: number;
+  let maxX1: number;
+  let maxY1: number;
+  const minX2 = dividingExtent[0];
+  const minY2 = dividingExtent[1];
+  const maxX2 = dividingExtent[2];
+  const maxY2 = dividingExtent[3];
+  let leftExtent: Extent;
+  let rightExtent: Extent;
+  let bottomExtent: Extent;
+  let topExtent: Extent;
+  const dividedExtents: Array<Extent> = [];
+
+  extentsArray.forEach(extent => {
+    [minX1, minY1, maxX1, maxY1] = [...extent];
+
+    const intersects = minX1 < maxX2 && maxX1 > minX2 && minY1 < maxY2 && maxY1 > minY2;
+    if (intersects) {
+      if (minX2 <= minX1 && minY2 <= minY1 && maxX2 >= maxX1 && maxY2 >= maxY1) {
+        return;
+      }
+      if (minX2 > minX1) {
+        leftExtent = [minX1, minY1, minX2, maxY1];
+        dividedExtents.push(leftExtent);
+      }
+      if (minX2 < maxX1) {
+        rightExtent = [maxX2, minY1, maxX1, maxY1];
+        dividedExtents.push(rightExtent);
+      }
+      if (maxY2 < maxY1) {
+        topExtent = [minX1, maxY2, maxX1, maxY1];
+        dividedExtents.push(topExtent);
+      }
+      if (minY2 > minY1) {
+        bottomExtent = [minX1, minY1, maxX1, minY2];
+        dividedExtents.push(bottomExtent);
+      }
+    } else {
+      dividedExtents.push(extent);
+    }
+  });
+  return dividedExtents;
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
index a28643fb..fab80515 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
@@ -2,7 +2,7 @@
 import Polygon from 'ol/geom/Polygon';
 import { Style } from 'ol/style';
 import Feature, { FeatureLike } from 'ol/Feature';
-import { Geometry, MultiPolygon } from 'ol/geom';
+import { MultiPolygon } from 'ol/geom';
 import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
 import {
   HorizontalAlign,
@@ -19,6 +19,10 @@ import {
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import VectorSource from 'ol/source/Vector';
 import MapBackgroundsEnum from '@/redux/map/map.enums';
+import { Extent } from 'ol/extent';
+import { MapSize } from '@/redux/map/map.types';
+import getCoverStyles from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles';
+import handleSemanticView from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView';
 
 export interface BaseMapElementProps {
   type: string;
@@ -45,6 +49,7 @@ export interface BaseMapElementProps {
   pointToProjection: UsePointToProjectionResult;
   vectorSource: VectorSource;
   mapBackgroundType: number;
+  mapSize: MapSize;
 }
 
 export default abstract class BaseMultiPolygon {
@@ -102,6 +107,8 @@ export default abstract class BaseMultiPolygon {
 
   mapBackgroundType: number;
 
+  mapSize: MapSize;
+
   constructor({
     type,
     sboTerm,
@@ -127,6 +134,7 @@ export default abstract class BaseMultiPolygon {
     pointToProjection,
     vectorSource,
     mapBackgroundType,
+    mapSize,
   }: BaseMapElementProps) {
     this.type = type;
     this.sboTerm = sboTerm;
@@ -152,6 +160,7 @@ export default abstract class BaseMultiPolygon {
     this.pointToProjection = pointToProjection;
     this.vectorSource = vectorSource;
     this.mapBackgroundType = mapBackgroundType;
+    this.mapSize = mapSize;
   }
 
   protected abstract createPolygons(): void;
@@ -188,6 +197,7 @@ export default abstract class BaseMultiPolygon {
   protected drawMultiPolygonFeature(mapInstance: MapInstance): void {
     this.feature = new Feature({
       geometry: new MultiPolygon(this.polygons),
+      zIndex: this.zIndex,
       getScale: (resolution: number): number => {
         const maxZoom = mapInstance?.getView().get('originalMaxZoom');
         if (maxZoom) {
@@ -226,54 +236,33 @@ export default abstract class BaseMultiPolygon {
   }
 
   protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
+    if (!(feature instanceof Feature)) {
+      return undefined;
+    }
     const styles: Array<Style> = [];
     const getScale = feature.get('getScale');
-    const getMapExtent = feature.get('getMapExtent');
     let scale = 1;
     let cover = false;
-    let coverRation: number = 1;
+    let largestExtent: Extent | null;
+
     if (getScale instanceof Function) {
       scale = getScale(resolution);
     }
 
     let hide = false;
     if (this.mapBackgroundType === MapBackgroundsEnum.SEMANTIC) {
-      if (getMapExtent instanceof Function && this.type === 'COMPARTMENT') {
-        const mapExtent = getMapExtent(resolution);
-        const featureExtent = feature.getGeometry()?.getExtent();
-        if (featureExtent && mapExtent) {
-          const mapArea =
-            Math.abs(mapExtent[2] - mapExtent[0]) * Math.abs(mapExtent[3] - mapExtent[1]);
-          const compartmentArea =
-            Math.abs(featureExtent[2] - featureExtent[0]) *
-            Math.abs(featureExtent[3] - featureExtent[1]);
-          coverRation = compartmentArea / mapArea;
-          if (coverRation < 0.05 && scale < 1) {
-            cover = true;
-          }
-          (feature as Feature).set('filled', cover);
-        }
-      }
+      const semanticViewData = handleSemanticView(
+        this.vectorSource,
+        feature,
+        resolution,
+        scale,
+        this.compartmentId,
+        this.complexId,
+      );
+      cover = semanticViewData.cover;
+      hide = semanticViewData.hide;
+      largestExtent = semanticViewData.largestExtent;
 
-      let complex: Feature<Geometry> | null;
-      let compartment: Feature<Geometry> | null;
-      if (this.complexId) {
-        complex = this.vectorSource.getFeatureById(this.complexId);
-        if (complex) {
-          if (complex.get('hidden')) {
-            hide = true;
-          }
-        }
-      }
-      if (this.compartmentId) {
-        compartment = this.vectorSource.getFeatureById(this.compartmentId);
-        if (compartment) {
-          if (compartment.get('filled')) {
-            hide = true;
-          }
-        }
-      }
-      (feature as Feature).set('hidden', hide);
       if (hide) {
         return undefined;
       }
@@ -290,17 +279,27 @@ export default abstract class BaseMultiPolygon {
       if (styleGeometry instanceof Polygon) {
         type = styleGeometry.get('type');
         text = styleGeometry.get('text');
-        fontSize = styleGeometry.get('fontSize');
+        fontSize = styleGeometry.get('fontSize') || 10;
         lineWidth = styleGeometry.get('lineWidth');
         coverStyle = styleGeometry.get('coverStyle');
       }
+
       if (cover) {
-        if (coverStyle) {
-          coverStyle.setZIndex(this.zIndex + 1000);
-          styles.push(coverStyle);
+        if (coverStyle && largestExtent) {
+          styles.push(
+            ...getCoverStyles(
+              coverStyle,
+              largestExtent,
+              this.text,
+              scale,
+              this.zIndex + 1000,
+              this.mapSize,
+            ),
+          );
         }
         return;
       }
+
       if (
         [MAP_ELEMENT_TYPES.MODIFICATION, MAP_ELEMENT_TYPES.TEXT].includes(type) &&
         scale * fontSize <= 4
@@ -329,6 +328,7 @@ export default abstract class BaseMultiPolygon {
       }
       styles.push(clonedStyle);
     });
+
     return styles;
   }
 }
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
index c61b16d1..a74beed4 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
@@ -15,6 +15,7 @@ import { MapInstance } from '@/types/map';
 import { Color } from '@/types/models';
 import { MAP_ELEMENT_TYPES } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import VectorSource from 'ol/source/Vector';
+import { MapSize } from '@/redux/map/map.types';
 
 export interface CompartmentProps {
   id: number;
@@ -43,6 +44,7 @@ export interface CompartmentProps {
   mapInstance: MapInstance;
   vectorSource: VectorSource;
   mapBackgroundType: number;
+  mapSize: MapSize;
 }
 
 export default abstract class Compartment extends BaseMultiPolygon {
@@ -83,6 +85,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
     mapInstance,
     vectorSource,
     mapBackgroundType,
+    mapSize,
   }: CompartmentProps) {
     super({
       type: 'COMPARTMENT',
@@ -108,6 +111,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
       pointToProjection,
       vectorSource,
       mapBackgroundType,
+      mapSize,
     });
     this.outerWidth = outerWidth;
     this.innerWidth = innerWidth;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
index ba61e067..376e0408 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
@@ -19,6 +19,7 @@ import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/s
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 import VectorSource from 'ol/source/Vector';
 import MapBackgroundsEnum from '@/redux/map/map.enums';
+import { DEFAULT_TILE_SIZE } from '@/constants/map';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -68,6 +69,13 @@ describe('CompartmentCircle', () => {
       mapInstance,
       vectorSource: new VectorSource(),
       mapBackgroundType: MapBackgroundsEnum.SEMANTIC,
+      mapSize: {
+        minZoom: 1,
+        maxZoom: 9,
+        width: 0,
+        height: 0,
+        tileSize: DEFAULT_TILE_SIZE,
+      },
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
index 040cc8ee..bd80b095 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
@@ -15,6 +15,7 @@ import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/s
 import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment';
 import { Color } from '@/types/models';
 import VectorSource from 'ol/source/Vector';
+import { MapSize } from '@/redux/map/map.types';
 
 export type CompartmentCircleProps = {
   id: number;
@@ -43,6 +44,7 @@ export type CompartmentCircleProps = {
   mapInstance: MapInstance;
   vectorSource: VectorSource;
   mapBackgroundType: number;
+  mapSize: MapSize;
 };
 
 export default class CompartmentCircle extends Compartment {
@@ -73,6 +75,7 @@ export default class CompartmentCircle extends Compartment {
     mapInstance,
     vectorSource,
     mapBackgroundType,
+    mapSize,
   }: CompartmentCircleProps) {
     super({
       id,
@@ -101,6 +104,7 @@ export default class CompartmentCircle extends Compartment {
       mapInstance,
       vectorSource,
       mapBackgroundType,
+      mapSize,
     });
   }
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
index 26bf658b..ef3d8cde 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
@@ -19,6 +19,7 @@ import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/s
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 import VectorSource from 'ol/source/Vector';
 import MapBackgroundsEnum from '@/redux/map/map.enums';
+import { DEFAULT_TILE_SIZE } from '@/constants/map';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -66,6 +67,13 @@ describe('CompartmentPathway', () => {
       mapInstance,
       vectorSource: new VectorSource(),
       mapBackgroundType: MapBackgroundsEnum.SEMANTIC,
+      mapSize: {
+        minZoom: 1,
+        maxZoom: 9,
+        width: 0,
+        height: 0,
+        tileSize: DEFAULT_TILE_SIZE,
+      },
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
index 0fc13627..92bb522d 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
@@ -18,6 +18,7 @@ import VectorSource from 'ol/source/Vector';
 import { Style } from 'ol/style';
 import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
 import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
+import { MapSize } from '@/redux/map/map.types';
 
 export type CompartmentPathwayProps = {
   id: number;
@@ -44,6 +45,7 @@ export type CompartmentPathwayProps = {
   mapInstance: MapInstance;
   vectorSource: VectorSource;
   mapBackgroundType: number;
+  mapSize: MapSize;
 };
 
 export default class CompartmentPathway extends BaseMultiPolygon {
@@ -74,6 +76,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
     mapInstance,
     vectorSource,
     mapBackgroundType,
+    mapSize,
   }: CompartmentPathwayProps) {
     super({
       type: 'COMPARTMENT',
@@ -99,6 +102,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
       pointToProjection,
       vectorSource,
       mapBackgroundType,
+      mapSize,
     });
     this.outerWidth = outerWidth;
     this.createPolygons();
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
index ae4148d2..ac2f9d52 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
@@ -18,6 +18,7 @@ import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/s
 import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords';
 import VectorSource from 'ol/source/Vector';
 import MapBackgroundsEnum from '@/redux/map/map.enums';
+import { DEFAULT_TILE_SIZE } from '@/constants/map';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -66,6 +67,13 @@ describe('CompartmentSquare', () => {
       mapInstance,
       vectorSource: new VectorSource(),
       mapBackgroundType: MapBackgroundsEnum.SEMANTIC,
+      mapSize: {
+        minZoom: 1,
+        maxZoom: 9,
+        width: 0,
+        height: 0,
+        tileSize: DEFAULT_TILE_SIZE,
+      },
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
index 1732c177..f5f1df81 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
@@ -14,6 +14,7 @@ import getPolygonCoords from '@/components/Map/MapViewer/MapViewerVector/utils/s
 import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment';
 import { Color } from '@/types/models';
 import VectorSource from 'ol/source/Vector';
+import { MapSize } from '@/redux/map/map.types';
 
 export type CompartmentSquareProps = {
   id: number;
@@ -42,6 +43,7 @@ export type CompartmentSquareProps = {
   mapInstance: MapInstance;
   vectorSource: VectorSource;
   mapBackgroundType: number;
+  mapSize: MapSize;
 };
 
 export default class CompartmentSquare extends Compartment {
@@ -72,6 +74,7 @@ export default class CompartmentSquare extends Compartment {
     mapInstance,
     vectorSource,
     mapBackgroundType,
+    mapSize,
   }: CompartmentSquareProps) {
     super({
       id,
@@ -100,6 +103,7 @@ export default class CompartmentSquare extends Compartment {
       mapInstance,
       vectorSource,
       mapBackgroundType,
+      mapSize,
     });
   }
 
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
index 1611b7fe..a3c6c679 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts
@@ -19,6 +19,7 @@ import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shap
 import { shapesFixture } from '@/models/fixtures/shapesFixture';
 import VectorSource from 'ol/source/Vector';
 import MapBackgroundsEnum from '@/redux/map/map.enums';
+import { DEFAULT_TILE_SIZE } from '@/constants/map';
 
 jest.mock('../text/getTextStyle');
 jest.mock('../text/getTextCoords');
@@ -68,6 +69,13 @@ describe('MapElement', () => {
       vectorSource: new VectorSource(),
       getOverlayColor: (): string => '#ffffff',
       mapBackgroundType: MapBackgroundsEnum.SEMANTIC,
+      mapSize: {
+        minZoom: 1,
+        maxZoom: 9,
+        width: 0,
+        height: 0,
+        tileSize: DEFAULT_TILE_SIZE,
+      },
     };
 
     (getTextStyle as jest.Mock).mockReturnValue(
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
index 4a0cd82c..826e5fca 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
@@ -29,6 +29,7 @@ import { ZERO } from '@/constants/common';
 import { OverlayOrder } from '@/redux/overlayBioEntity/overlayBioEntity.utils';
 import { GetOverlayBioEntityColorByAvailableProperties } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
 import VectorSource from 'ol/source/Vector';
+import { MapSize } from '@/redux/map/map.types';
 
 export type MapElementProps = {
   id: number;
@@ -66,6 +67,7 @@ export type MapElementProps = {
   overlaysOrder?: Array<OverlayOrder>;
   getOverlayColor: GetOverlayBioEntityColorByAvailableProperties;
   mapBackgroundType: number;
+  mapSize: MapSize;
 };
 
 export default class MapElement extends BaseMultiPolygon {
@@ -129,6 +131,7 @@ export default class MapElement extends BaseMultiPolygon {
     overlaysOrder = [],
     getOverlayColor,
     mapBackgroundType,
+    mapSize,
   }: MapElementProps) {
     super({
       type: FEATURE_TYPE.ALIAS,
@@ -155,6 +158,7 @@ export default class MapElement extends BaseMultiPolygon {
       pointToProjection,
       vectorSource,
       mapBackgroundType,
+      mapSize,
     });
     this.shapes = shapes;
     this.lineWidth = lineWidth;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.test.ts
new file mode 100644
index 00000000..0b7e01e1
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.test.ts
@@ -0,0 +1,112 @@
+/* eslint-disable no-magic-numbers */
+import Feature from 'ol/Feature';
+import VectorSource from 'ol/source/Vector';
+import getDividedExtents from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getDividedExtents';
+import findLargestExtent from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/findLargestExtent';
+import handleSemanticView from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView';
+import Geometry from 'ol/geom/Geometry';
+import { fromExtent } from 'ol/geom/Polygon';
+
+jest.mock('../coords/getDividedExtents');
+jest.mock('../coords/findLargestExtent');
+
+describe('handleSemanticView', () => {
+  let vectorSource: VectorSource;
+  let feature: Feature;
+
+  beforeEach(() => {
+    vectorSource = new VectorSource();
+    feature = new Feature({
+      geometry: fromExtent([0, 0, 100, 100]),
+      type: 'COMPARTMENT',
+      zIndex: 1,
+      getMapExtent: (): Array<number> => [0, 0, 100, 100],
+      filled: false,
+      hidden: false,
+    });
+
+    const mockGeometry = {
+      getExtent: jest.fn(() => [2, 0, 10, 10]),
+    } as unknown as Geometry;
+
+    feature.getGeometry = jest.fn(() => mockGeometry);
+  });
+
+  it('should return cover = true, hide = false, and calculate largestExtent when feature meets cover conditions', () => {
+    jest
+      .spyOn(vectorSource, 'forEachFeatureIntersectingExtent')
+      .mockImplementation((_, callback) => {
+        callback(
+          new Feature({
+            geometry: fromExtent([1, 0, 5, 5]),
+            hidden: false,
+            type: 'COMPARTMENT',
+            zIndex: 123,
+            filled: true,
+            getMapExtent: (): Array<number> => [1, 0, 5, 5],
+          }),
+        );
+      });
+    (getDividedExtents as jest.Mock).mockReturnValue([[0, 0, 10, 5]]);
+    (findLargestExtent as jest.Mock).mockReturnValue([0, 0, 10, 5]);
+
+    const result = handleSemanticView(vectorSource, feature, 1, 0.5, null);
+
+    expect(result).toEqual({
+      cover: true,
+      hide: false,
+      largestExtent: [0, 0, 10, 5],
+    });
+
+    expect(feature.get('filled')).toBe(true);
+    expect(getDividedExtents).toHaveBeenCalled();
+    expect(findLargestExtent).toHaveBeenCalled();
+  });
+
+  it('should return hide = true when complexId points to a hidden feature', () => {
+    const complexFeature = new Feature({ hidden: true });
+    jest
+      .spyOn(vectorSource, 'getFeatureById')
+      .mockImplementation(id => (id === 1 ? complexFeature : null));
+
+    const result = handleSemanticView(vectorSource, feature, 1, 1, null, 1);
+
+    expect(result).toEqual({
+      cover: false,
+      hide: true,
+      largestExtent: null,
+    });
+
+    expect(feature.get('hidden')).toBe(true);
+  });
+
+  it('should return hide = true when compartmentId points to a filled feature', () => {
+    const compartmentFeature = new Feature({ filled: true });
+    jest
+      .spyOn(vectorSource, 'getFeatureById')
+      .mockImplementation(id => (id === 2 ? compartmentFeature : null));
+
+    const result = handleSemanticView(vectorSource, feature, 1, 1, 2);
+
+    expect(result).toEqual({
+      cover: false,
+      hide: true,
+      largestExtent: null,
+    });
+
+    expect(feature.get('hidden')).toBe(true);
+  });
+
+  it('should return cover = false and hide = false when feature does not meet any conditions', () => {
+    const result = handleSemanticView(vectorSource, feature, 1, 1, null);
+
+    expect(result).toEqual({
+      cover: false,
+      hide: false,
+      largestExtent: null,
+    });
+
+    expect(feature.get('filled')).toBe(false);
+    expect(feature.get('hidden')).toBe(false);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts
new file mode 100644
index 00000000..83269b75
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts
@@ -0,0 +1,68 @@
+/* eslint-disable no-magic-numbers */
+import getDividedExtents from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getDividedExtents';
+import findLargestExtent from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/findLargestExtent';
+import Feature from 'ol/Feature';
+import VectorSource from 'ol/source/Vector';
+import { Extent } from 'ol/extent';
+
+export default function handleSemanticView(
+  vectorSource: VectorSource,
+  feature: Feature,
+  resolution: number,
+  scale: number,
+  compartmentId: number | null,
+  complexId?: number | null,
+): { cover: boolean; hide: boolean; largestExtent: Extent | null } {
+  const type = feature.get('type');
+  const getMapExtent = feature.get('getMapExtent');
+  let coverRatio = 1;
+  let cover = false;
+  let hide = false;
+  let largestExtent: Extent | null = null;
+  if (getMapExtent instanceof Function && type === 'COMPARTMENT') {
+    const mapExtent = getMapExtent(resolution);
+    const featureExtent = feature.getGeometry()?.getExtent();
+    if (featureExtent && mapExtent) {
+      const mapArea = Math.abs(mapExtent[2] - mapExtent[0]) * Math.abs(mapExtent[3] - mapExtent[1]);
+      const compartmentArea =
+        Math.abs(featureExtent[2] - featureExtent[0]) *
+        Math.abs(featureExtent[3] - featureExtent[1]);
+      coverRatio = compartmentArea / mapArea;
+      if (coverRatio < 0.05 && scale < 1) {
+        cover = true;
+        let remainingExtents = [featureExtent];
+        vectorSource.forEachFeatureIntersectingExtent(featureExtent, intersectingFeature => {
+          if (
+            !intersectingFeature.get('hidden') &&
+            intersectingFeature.get('type') === 'COMPARTMENT' &&
+            intersectingFeature.get('zIndex') > feature.get('zIndex') &&
+            intersectingFeature.get('filled')
+          ) {
+            const intersectingFeatureExtent = intersectingFeature.getGeometry()?.getExtent();
+            if (intersectingFeatureExtent) {
+              remainingExtents = getDividedExtents(remainingExtents, intersectingFeatureExtent);
+            }
+          }
+        });
+        largestExtent = findLargestExtent(remainingExtents) || featureExtent;
+      }
+      (feature as Feature).set('filled', cover);
+    }
+  }
+
+  if (complexId) {
+    const complex = vectorSource.getFeatureById(complexId);
+    if (complex && complex.get('hidden')) {
+      hide = true;
+    }
+  }
+  if (compartmentId) {
+    const compartment = vectorSource.getFeatureById(compartmentId);
+    if (compartment && compartment.get('filled')) {
+      hide = true;
+    }
+  }
+  (feature as Feature).set('hidden', hide);
+
+  return { cover, hide, largestExtent };
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.test.ts
new file mode 100644
index 00000000..0e22293c
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.test.ts
@@ -0,0 +1,88 @@
+/* eslint-disable no-magic-numbers */
+import Style from 'ol/style/Style';
+import { Extent } from 'ol/extent';
+import getWrappedTextWithFontSize from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getWrappedTextWithFontSize';
+import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle';
+import { latLngToPoint } from '@/utils/map/latLngToPoint';
+import getCoverStyles from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles';
+import { DEFAULT_TILE_SIZE } from '@/constants/map';
+
+jest.mock('../text/getWrappedTextWithFontSize');
+jest.mock('../text/getTextStyle');
+jest.mock('../../../../../../../utils/map/latLngToPoint');
+
+describe('getCoverStyles', () => {
+  it('should return cover and text styles based on the provided parameters', () => {
+    const coverStyle = new Style();
+    const largestExtent: Extent = [10, 10, 50, 50];
+    const text = 'Sample Text';
+    const scale = 1;
+    const zIndex = 5;
+    const mapSize = {
+      width: 1000,
+      height: 800,
+      minZoom: 1,
+      maxZoom: 9,
+      tileSize: DEFAULT_TILE_SIZE,
+    };
+
+    (latLngToPoint as jest.Mock).mockImplementation(([lat, lng], size) => ({
+      x: (lng * size.width) / 100,
+      y: (lat * size.height) / 100,
+    }));
+
+    (getWrappedTextWithFontSize as jest.Mock).mockReturnValue({
+      text: 'Sample\nText',
+      fontSize: 12,
+    });
+
+    const mockTextStyle = new Style();
+    (getTextStyle as jest.Mock).mockReturnValue(mockTextStyle);
+
+    const result = getCoverStyles(coverStyle, largestExtent, text, scale, zIndex, mapSize);
+
+    expect(result).toHaveLength(2);
+    expect(result[0]).toBe(coverStyle);
+    expect(coverStyle.getZIndex()).toBe(zIndex);
+
+    expect(result[1]).toBe(mockTextStyle);
+    expect(getWrappedTextWithFontSize).toHaveBeenCalledWith({
+      text,
+      maxWidth: expect.any(Number),
+      maxHeight: expect.any(Number),
+    });
+
+    expect(getTextStyle).toHaveBeenCalledWith({
+      text: 'Sample\nText',
+      fontSize: 12,
+      color: '#000',
+      zIndex,
+      horizontalAlign: 'CENTER',
+    });
+  });
+
+  it('should handle empty text gracefully', () => {
+    const coverStyle = new Style();
+    const largestExtent: Extent = [10, 10, 50, 50];
+    const text = '';
+    const scale = 1;
+    const zIndex = 5;
+    const mapSize = {
+      width: 1000,
+      height: 800,
+      minZoom: 1,
+      maxZoom: 9,
+      tileSize: DEFAULT_TILE_SIZE,
+    };
+
+    (getWrappedTextWithFontSize as jest.Mock).mockReturnValue({
+      text: '',
+      fontSize: 0,
+    });
+
+    const result = getCoverStyles(coverStyle, largestExtent, text, scale, zIndex, mapSize);
+
+    expect(result).toHaveLength(1);
+    expect(result[0]).toBe(coverStyle);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.ts
new file mode 100644
index 00000000..ce072c86
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.ts
@@ -0,0 +1,50 @@
+/* eslint-disable no-magic-numbers */
+import Style from 'ol/style/Style';
+import { Extent, getCenter } from 'ol/extent';
+import { toLonLat } from 'ol/proj';
+import { latLngToPoint } from '@/utils/map/latLngToPoint';
+import getWrappedTextWithFontSize from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getWrappedTextWithFontSize';
+import { Point } from 'ol/geom';
+import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle';
+import { MapSize } from '@/redux/map/map.types';
+
+export default function getCoverStyles(
+  coverStyle: Style,
+  largestExtent: Extent,
+  text: string,
+  scale: number,
+  zIndex: number,
+  mapSize: MapSize,
+): Array<Style> {
+  const styles: Array<Style> = [];
+  coverStyle.setZIndex(zIndex);
+  styles.push(coverStyle);
+
+  if (text) {
+    const [lng1, lat1] = toLonLat([largestExtent[0], largestExtent[1]]);
+    const [lng2, lat2] = toLonLat([largestExtent[2], largestExtent[3]]);
+    const point1 = latLngToPoint([lat1, lng1], mapSize);
+    const point2 = latLngToPoint([lat2, lng2], mapSize);
+    const maxWidth = point2.x - point1.x;
+    const maxHeight = Math.abs(Math.abs(point2.y) - Math.abs(point1.y));
+    const { text: brokenText, fontSize: calculatedFontSize } = getWrappedTextWithFontSize({
+      text,
+      maxWidth: maxWidth * scale * 0.9,
+      maxHeight: maxHeight * scale * 0.9,
+    });
+    const center = getCenter(largestExtent);
+    const textGeometry = new Point([center[0], center[1]]);
+
+    const textStyle = getTextStyle({
+      text: brokenText.trim(),
+      fontSize: calculatedFontSize,
+      color: '#000',
+      zIndex,
+      horizontalAlign: 'CENTER',
+    });
+    textStyle.setGeometry(textGeometry);
+    styles.push(textStyle);
+  }
+
+  return styles;
+}
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
index 4858fb21..94ab9f92 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
@@ -8,7 +8,7 @@ export default function getTextStyle({
   color,
   zIndex,
   horizontalAlign,
-  overflow = true,
+  overflow = false,
 }: {
   text: string;
   fontSize: number;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getWrappedTextWithFontSize.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getWrappedTextWithFontSize.test.ts
new file mode 100644
index 00000000..2cfaa243
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getWrappedTextWithFontSize.test.ts
@@ -0,0 +1,49 @@
+/* eslint-disable no-magic-numbers */
+import getWrappedTextWithFontSize from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getWrappedTextWithFontSize';
+
+describe('getWrappedTextWithFontSize', () => {
+  it('should return a wrapped text and font size for this text when maxWidth is limited and maxHeight is unlimited', () => {
+    const text = 'Wrapped text with font size test';
+    const maxWidth = 15;
+    const maxHeight = 9999;
+
+    const { text: wrappedText, fontSize } = getWrappedTextWithFontSize({
+      text,
+      maxWidth,
+      maxHeight,
+    });
+
+    expect(wrappedText.trim()).toEqual('Wrapped text\nwith font size\ntest');
+    expect(fontSize).toEqual(12);
+  });
+
+  it('should return a wrapped text and font size for this text when maxWidth is unlimited and maxHeight is limited', () => {
+    const text = 'Wrapped text with font size test';
+    const maxWidth = 9999;
+    const maxHeight = 9;
+
+    const { text: wrappedText, fontSize } = getWrappedTextWithFontSize({
+      text,
+      maxWidth,
+      maxHeight,
+    });
+
+    expect(wrappedText.trim()).toEqual('Wrapped text with font size test');
+    expect(fontSize).toEqual(4);
+  });
+
+  it('should return a wrapped text and font size for this text when maxWidth is limited and maxHeight is limited', () => {
+    const text = 'Wrapped text with font size test';
+    const maxWidth = 20;
+    const maxHeight = 9;
+
+    const { text: wrappedText, fontSize } = getWrappedTextWithFontSize({
+      text,
+      maxWidth,
+      maxHeight,
+    });
+
+    expect(wrappedText.trim()).toEqual('Wrapped text with\nfont size test');
+    expect(fontSize).toEqual(3);
+  });
+});
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getWrappedTextWithFontSize.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getWrappedTextWithFontSize.ts
new file mode 100644
index 00000000..9398d90b
--- /dev/null
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getWrappedTextWithFontSize.ts
@@ -0,0 +1,59 @@
+/* eslint-disable no-magic-numbers */
+export default function getWrappedTextWithFontSize({
+  text,
+  maxWidth,
+  maxHeight,
+  minFontSize = 3,
+  maxFontSize = 12,
+}: {
+  text: string;
+  maxWidth: number;
+  maxHeight: number;
+  minFontSize?: number;
+  maxFontSize?: number;
+}): { text: string; fontSize: number } {
+  const result = {
+    text,
+    fontSize: maxFontSize,
+  };
+  const canvas = document.createElement('canvas');
+  const context = canvas.getContext('2d');
+  if (!context) {
+    return result;
+  }
+
+  let resultFontSize = maxFontSize;
+  let resultText = text;
+  for (let testFontSize = maxFontSize; testFontSize >= minFontSize; testFontSize -= 1) {
+    context.font = `${testFontSize}px Arial`;
+    let currentLine = '';
+    let splittedText = '';
+    resultFontSize = testFontSize;
+    text.split(' ').forEach((word: string) => {
+      const testLine = currentLine ? `${currentLine} ${word}` : word;
+      const testWidth = context.measureText(testLine).width;
+
+      if (testWidth > maxWidth && currentLine) {
+        splittedText += `\n${currentLine}`;
+        currentLine = word;
+      } else {
+        currentLine = testLine;
+      }
+    });
+    if (currentLine) {
+      splittedText += `\n${currentLine}`;
+    }
+    const lines = splittedText.split('\n');
+    const maxLineWidth = lines.reduce(
+      (maxFoundWidth, line) => Math.max(maxFoundWidth, context.measureText(line).width),
+      0,
+    );
+    if (maxLineWidth <= maxWidth && testFontSize * lines.length <= maxHeight) {
+      resultText = splittedText;
+      break;
+    }
+  }
+  result.text = resultText;
+  result.fontSize = resultFontSize;
+  return result;
+}
-- 
GitLab


From dfb75c2bca0094cea7dc378fafe863b3290cd27f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Mon, 25 Nov 2024 15:09:16 +0100
Subject: [PATCH 22/29] fix(vector-map): set default overflow value to true in
 getTextStyle

---
 .../MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
index 94ab9f92..4858fb21 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle.ts
@@ -8,7 +8,7 @@ export default function getTextStyle({
   color,
   zIndex,
   horizontalAlign,
-  overflow = false,
+  overflow = true,
 }: {
   text: string;
   fontSize: number;
-- 
GitLab


From 2b06650833b74b822afb0623f447f85110c9544e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Mon, 25 Nov 2024 15:44:12 +0100
Subject: [PATCH 23/29] feat(vector-map): disable elements background color
 when data overlays are visible

---
 .../reactionsLayer/processModelElements.ts    |  1 +
 .../utils/shapes/elements/Compartment.ts      | 21 +++++++++++++++----
 .../shapes/elements/CompartmentCircle.test.ts |  1 +
 .../shapes/elements/CompartmentCircle.ts      |  3 +++
 .../elements/CompartmentPathway.test.ts       |  1 +
 .../shapes/elements/CompartmentPathway.ts     |  7 ++++++-
 .../shapes/elements/CompartmentSquare.test.ts |  1 +
 .../shapes/elements/CompartmentSquare.ts      |  3 +++
 .../utils/shapes/elements/MapElement.ts       |  2 +-
 9 files changed, 34 insertions(+), 6 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
index 2063c53c..51afc92f 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
@@ -73,6 +73,7 @@ export default function processModelElements(
         nameHorizontalAlign: element.nameHorizontalAlign as HorizontalAlign,
         text: element.name,
         fontSize: element.fontSize,
+        overlaysVisible: Boolean(overlaysOrder.length),
         pointToProjection,
         mapInstance,
         vectorSource,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
index a74beed4..84bbba02 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts
@@ -40,6 +40,7 @@ export interface CompartmentProps {
   nameHorizontalAlign: HorizontalAlign;
   fillColor: Color;
   borderColor: Color;
+  overlaysVisible: boolean;
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
   vectorSource: VectorSource;
@@ -58,6 +59,8 @@ export default abstract class Compartment extends BaseMultiPolygon {
 
   thickness: number;
 
+  overlaysVisible: boolean;
+
   constructor({
     id,
     complexId,
@@ -81,6 +84,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
     nameHorizontalAlign,
     fillColor,
     borderColor,
+    overlaysVisible,
     pointToProjection,
     mapInstance,
     vectorSource,
@@ -116,6 +120,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
     this.outerWidth = outerWidth;
     this.innerWidth = innerWidth;
     this.thickness = thickness;
+    this.overlaysVisible = overlaysVisible;
     this.getCompartmentCoords();
     this.createPolygons();
     this.drawText();
@@ -142,7 +147,9 @@ export default abstract class Compartment extends BaseMultiPolygon {
     this.styles.push(
       new Style({
         geometry: framePolygon,
-        fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 128 }) }),
+        fill: this.overlaysVisible
+          ? undefined
+          : getFill({ color: rgbToHex({ ...this.fillColor, alpha: 128 }) }),
         zIndex: this.zIndex,
       }),
     );
@@ -154,7 +161,9 @@ export default abstract class Compartment extends BaseMultiPolygon {
     this.styles.push(
       new Style({
         geometry: outerPolygon,
-        stroke: getStroke({ color: rgbToHex(this.borderColor), width: this.outerWidth }),
+        stroke: this.overlaysVisible
+          ? getStroke({ width: this.outerWidth })
+          : getStroke({ color: rgbToHex(this.borderColor), width: this.outerWidth }),
         zIndex: this.zIndex,
       }),
     );
@@ -166,8 +175,12 @@ export default abstract class Compartment extends BaseMultiPolygon {
     this.styles.push(
       new Style({
         geometry: innerPolygon,
-        stroke: getStroke({ color: rgbToHex(this.borderColor), width: this.innerWidth }),
-        fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 9 }) }),
+        stroke: this.overlaysVisible
+          ? getStroke({ width: this.innerWidth })
+          : getStroke({ color: rgbToHex(this.borderColor), width: this.innerWidth }),
+        fill: this.overlaysVisible
+          ? undefined
+          : getFill({ color: rgbToHex({ ...this.fillColor, alpha: 9 }) }),
         zIndex: this.zIndex,
       }),
     );
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
index 376e0408..876dd762 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts
@@ -65,6 +65,7 @@ describe('CompartmentCircle', () => {
       nameWidth: 40,
       nameVerticalAlign: 'MIDDLE',
       nameHorizontalAlign: 'CENTER',
+      overlaysVisible: false,
       pointToProjection: jest.fn(),
       mapInstance,
       vectorSource: new VectorSource(),
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
index bd80b095..3dd38183 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts
@@ -40,6 +40,7 @@ export type CompartmentCircleProps = {
   nameWidth: number;
   nameVerticalAlign?: VerticalAlign;
   nameHorizontalAlign?: HorizontalAlign;
+  overlaysVisible: boolean;
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
   vectorSource: VectorSource;
@@ -71,6 +72,7 @@ export default class CompartmentCircle extends Compartment {
     nameWidth,
     nameVerticalAlign = 'MIDDLE',
     nameHorizontalAlign = 'CENTER',
+    overlaysVisible,
     pointToProjection,
     mapInstance,
     vectorSource,
@@ -100,6 +102,7 @@ export default class CompartmentCircle extends Compartment {
       nameHorizontalAlign,
       fillColor,
       borderColor,
+      overlaysVisible,
       pointToProjection,
       mapInstance,
       vectorSource,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
index ef3d8cde..5e1df810 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts
@@ -63,6 +63,7 @@ describe('CompartmentPathway', () => {
       nameWidth: 40,
       nameVerticalAlign: 'MIDDLE',
       nameHorizontalAlign: 'CENTER',
+      overlaysVisible: false,
       pointToProjection: jest.fn(() => [10, 10]),
       mapInstance,
       vectorSource: new VectorSource(),
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
index 92bb522d..3c9f586a 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts
@@ -41,6 +41,7 @@ export type CompartmentPathwayProps = {
   nameWidth: number;
   nameVerticalAlign?: VerticalAlign;
   nameHorizontalAlign?: HorizontalAlign;
+  overlaysVisible: boolean;
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
   vectorSource: VectorSource;
@@ -51,6 +52,8 @@ export type CompartmentPathwayProps = {
 export default class CompartmentPathway extends BaseMultiPolygon {
   outerWidth: number;
 
+  overlaysVisible: boolean;
+
   constructor({
     id,
     complexId,
@@ -72,6 +75,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
     nameWidth,
     nameVerticalAlign = 'MIDDLE',
     nameHorizontalAlign = 'CENTER',
+    overlaysVisible,
     pointToProjection,
     mapInstance,
     vectorSource,
@@ -105,6 +109,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
       mapSize,
     });
     this.outerWidth = outerWidth;
+    this.overlaysVisible = overlaysVisible;
     this.createPolygons();
     this.drawText();
     this.drawMultiPolygonFeature(mapInstance);
@@ -131,7 +136,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
       getStyle({
         geometry: compartmentPolygon,
         borderColor: this.borderColor,
-        fillColor: { ...this.fillColor, alpha: 9 },
+        fillColor: this.overlaysVisible ? undefined : { ...this.fillColor, alpha: 9 },
         lineWidth: this.outerWidth,
         zIndex: this.zIndex,
       }),
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
index ac2f9d52..c7721148 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts
@@ -63,6 +63,7 @@ describe('CompartmentSquare', () => {
       nameWidth: 40,
       nameVerticalAlign: 'MIDDLE',
       nameHorizontalAlign: 'CENTER',
+      overlaysVisible: false,
       pointToProjection: jest.fn(),
       mapInstance,
       vectorSource: new VectorSource(),
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
index f5f1df81..b4905d61 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts
@@ -39,6 +39,7 @@ export type CompartmentSquareProps = {
   nameWidth: number;
   nameVerticalAlign?: VerticalAlign;
   nameHorizontalAlign?: HorizontalAlign;
+  overlaysVisible: boolean;
   pointToProjection: UsePointToProjectionResult;
   mapInstance: MapInstance;
   vectorSource: VectorSource;
@@ -70,6 +71,7 @@ export default class CompartmentSquare extends Compartment {
     nameWidth,
     nameVerticalAlign = 'MIDDLE',
     nameHorizontalAlign = 'CENTER',
+    overlaysVisible,
     pointToProjection,
     mapInstance,
     vectorSource,
@@ -99,6 +101,7 @@ export default class CompartmentSquare extends Compartment {
       nameHorizontalAlign,
       fillColor,
       borderColor,
+      overlaysVisible,
       pointToProjection,
       mapInstance,
       vectorSource,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
index 826e5fca..fc005145 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts
@@ -299,7 +299,7 @@ export default class MapElement extends BaseMultiPolygon {
       const elementStyle = getStyle({
         geometry: elementPolygon,
         borderColor: this.borderColor,
-        fillColor: this.overlays.length ? undefined : this.fillColor,
+        fillColor: this.overlaysOrder.length ? undefined : this.fillColor,
         lineWidth: this.lineWidth,
         lineDash: this.lineDash,
         zIndex: this.zIndex,
-- 
GitLab


From a8371323e45a268a2f46e4509953c5c24609f45d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Tue, 26 Nov 2024 10:58:52 +0100
Subject: [PATCH 24/29] fix(vector-map): set zoom timeout for correct refresh
 of styles

---
 .../MapViewerVector/MapViewerVector.types.ts  |  2 ++
 .../reactionsLayer/useOlMapReactionsLayer.ts  |  2 ++
 .../utils/shapes/elements/BaseMultiPolygon.ts | 31 ++++++++++++++-----
 .../Map/MapViewer/utils/useOlMap.ts           |  4 +--
 src/constants/map.ts                          |  2 +-
 5 files changed, 31 insertions(+), 10 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts
index e7ccb7c9..63fddd60 100644
--- a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.types.ts
@@ -10,6 +10,8 @@ export type MapConfig = {
 export type VerticalAlign = 'TOP' | 'MIDDLE' | 'BOTTOM';
 export type HorizontalAlign = 'LEFT' | 'RIGHT' | 'CENTER' | 'END' | 'START';
 
+export type ScaleFunction = (resolution: number) => number;
+
 export type OverlayBioEntityGroupedElementsType = {
   [id: string]: Array<OverlayBioEntityRender & { amount: number }>;
 };
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index 4e289297..658e2a5d 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -195,6 +195,8 @@ export const useOlMapReactionsLayer = ({
   return useMemo(() => {
     const vectorLayer = new VectorLayer({
       source: vectorSource,
+      updateWhileAnimating: true,
+      updateWhileInteracting: true,
     });
     vectorLayer.set('type', VECTOR_MAP_LAYER_TYPE);
     return vectorLayer;
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
index fab80515..1960681d 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts
@@ -6,6 +6,7 @@ import { MultiPolygon } from 'ol/geom';
 import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
 import {
   HorizontalAlign,
+  ScaleFunction,
   VerticalAlign,
 } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
 import { MapInstance } from '@/types/map';
@@ -109,6 +110,11 @@ export default abstract class BaseMultiPolygon {
 
   mapSize: MapSize;
 
+  mapExtentCache: Map<number, [number, number, number, number]> = new Map<
+    number,
+    [number, number, number, number]
+  >();
+
   constructor({
     type,
     sboTerm,
@@ -198,17 +204,24 @@ export default abstract class BaseMultiPolygon {
     this.feature = new Feature({
       geometry: new MultiPolygon(this.polygons),
       zIndex: this.zIndex,
-      getScale: (resolution: number): number => {
+      getScale: ((): ScaleFunction => {
         const maxZoom = mapInstance?.getView().get('originalMaxZoom');
-        if (maxZoom) {
-          const minResolution = mapInstance?.getView().getResolutionForZoom(maxZoom);
+        const minResolution = maxZoom
+          ? mapInstance?.getView().getResolutionForZoom(maxZoom)
+          : undefined;
+
+        return (resolution: number): number => {
           if (minResolution) {
             return minResolution / resolution;
           }
-        }
-        return 1;
-      },
+          return 1;
+        };
+      })(),
       getMapExtent: (resolution: number): [number, number, number, number] | undefined => {
+        if (this.mapExtentCache.has(resolution)) {
+          return this.mapExtentCache.get(resolution);
+        }
+
         const view = mapInstance?.getView();
         const center = view?.getCenter();
         const size = mapInstance?.getSize();
@@ -216,15 +229,19 @@ export default abstract class BaseMultiPolygon {
         if (!size || !center) {
           return undefined;
         }
+
         const extentWidth = size[0] * resolution;
         const extentHeight = size[1] * resolution;
 
-        return [
+        const extent: [number, number, number, number] = [
           center[0] - extentWidth / 2,
           center[1] - extentHeight / 2,
           center[0] + extentWidth / 2,
           center[1] + extentHeight / 2,
         ];
+
+        this.mapExtentCache.set(resolution, extent);
+        return extent;
       },
       id: this.id,
       complexId: this.complexId,
diff --git a/src/components/Map/MapViewer/utils/useOlMap.ts b/src/components/Map/MapViewer/utils/useOlMap.ts
index de917a95..68ec65bd 100644
--- a/src/components/Map/MapViewer/utils/useOlMap.ts
+++ b/src/components/Map/MapViewer/utils/useOlMap.ts
@@ -65,8 +65,8 @@ export const useOlMap: UseOlMap = ({ target } = {}) => {
         mouseWheelZoom: false,
       }).extend([
         new MouseWheelZoom({
-          duration: 0,
-          timeout: 20,
+          duration: 250,
+          timeout: 80,
         }),
       ]),
       target: target || mapRef.current,
diff --git a/src/constants/map.ts b/src/constants/map.ts
index 07775a81..5856c87c 100644
--- a/src/constants/map.ts
+++ b/src/constants/map.ts
@@ -12,7 +12,7 @@ export const DEFAULT_CENTER_Y = 0;
 export const LATLNG_FALLBACK: LatLng = [0, 0];
 export const EXTENT_PADDING_MULTIPLICATOR = 1;
 
-export const ZOOM_RESCALING_FACTOR = 3;
+export const ZOOM_RESCALING_FACTOR = 2;
 
 export const DEFAULT_CENTER_POINT: Point = {
   x: DEFAULT_CENTER_X,
-- 
GitLab


From 73b83a8417738faa16df9e20466ed540beae6afc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Wed, 27 Nov 2024 13:11:20 +0100
Subject: [PATCH 25/29] refactor(vector-map): modify redux to store element of
 multiple models

---
 .../listeners/useOlMapVectorListeners.ts      | 20 +++---
 .../reactionsLayer/processModelElements.ts    |  6 +-
 .../reactionsLayer/useOlMapReactionsLayer.ts  | 63 ++++++++++++++-----
 .../MapViewer/utils/config/useOlMapView.ts    | 11 +---
 src/redux/apiPath.ts                          |  6 +-
 src/redux/modelElements/modelElements.mock.ts |  2 +-
 .../modelElements.reducers.test.ts            | 34 +++++-----
 .../modelElements/modelElements.reducers.ts   | 17 +++--
 .../modelElements/modelElements.selector.ts   | 17 +++--
 .../modelElements/modelElements.slice.ts      |  2 +-
 .../modelElements.thunks.test.ts              | 14 ++---
 .../modelElements/modelElements.thunks.ts     | 34 +++++-----
 .../modelElements/modelElements.types.ts      |  6 +-
 .../newReactions/newReactions.constants.ts    |  2 +-
 src/redux/newReactions/newReactions.mock.ts   |  2 +-
 .../newReactions.reducers.test.ts             | 32 +++++-----
 .../newReactions/newReactions.reducers.ts     | 17 +++--
 .../newReactions/newReactions.selectors.ts    |  9 ++-
 .../newReactions/newReactions.thunks.test.ts  | 10 +--
 src/redux/newReactions/newReactions.thunks.ts |  8 ++-
 src/redux/newReactions/newReactions.types.ts  |  4 +-
 21 files changed, 190 insertions(+), 126 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts
index 05993a81..444cf47b 100644
--- a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts
@@ -14,8 +14,8 @@ import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { Coordinate } from 'ol/coordinate';
 import { Pixel } from 'ol/pixel';
 import { onMapRightClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick';
-import { modelElementsSelector } from '@/redux/modelElements/modelElements.selector';
-import { newReactionsDataSelector } from '@/redux/newReactions/newReactions.selectors';
+import { modelElementsForCurrentModelSelector } from '@/redux/modelElements/modelElements.selector';
+import { newReactionsForCurrentModelSelector } from '@/redux/newReactions/newReactions.selectors';
 
 interface UseOlMapVectorListenersInput {
   mapInstance: MapInstance;
@@ -25,8 +25,8 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners
   const mapSize = useSelector(mapDataSizeSelector);
   const modelId = useSelector(currentModelIdSelector);
   const isResultDrawerOpen = useSelector(resultDrawerOpen);
-  const modelElements = useSelector(modelElementsSelector);
-  const reactions = useSelector(newReactionsDataSelector);
+  const modelElementsForCurrentModel = useSelector(modelElementsForCurrentModelSelector);
+  const newReactionsForCurrentModel = useSelector(newReactionsForCurrentModelSelector);
   const dispatch = useAppDispatch();
   const coordinate = useRef<Coordinate>([]);
   const pixel = useRef<Pixel>([]);
@@ -41,15 +41,21 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners
       dispatch,
       isResultDrawerOpen,
       comments,
-      modelElements?.content || [],
-      reactions,
+      modelElementsForCurrentModel || [],
+      newReactionsForCurrentModel,
     ),
     OPTIONS.clickPersistTime,
     { leading: false },
   );
 
   const handleRightClick = useDebouncedCallback(
-    onMapRightClick(mapSize, modelId, dispatch, modelElements?.content || [], reactions),
+    onMapRightClick(
+      mapSize,
+      modelId,
+      dispatch,
+      modelElementsForCurrentModel || [],
+      newReactionsForCurrentModel,
+    ),
     OPTIONS.clickPersistTime,
     {
       leading: false,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
index 51afc92f..8deeb01e 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts
@@ -1,4 +1,4 @@
-import { ModelElement, ModelElements } from '@/types/models';
+import { ModelElement } from '@/types/models';
 import MapElement from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement';
 import CompartmentCircle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle';
 import CompartmentSquare from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare';
@@ -18,7 +18,7 @@ import VectorSource from 'ol/source/Vector';
 import { MapSize } from '@/redux/map/map.types';
 
 export default function processModelElements(
-  modelElements: ModelElements,
+  modelElements: Array<ModelElement>,
   shapes: BioShapesDict,
   lineTypes: LineTypeDict,
   groupedElementsOverlays: Record<string, Array<OverlayBioEntityRender>>,
@@ -33,7 +33,7 @@ export default function processModelElements(
   const validElements: Array<
     MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph
   > = [];
-  modelElements.content.forEach((element: ModelElement) => {
+  modelElements.forEach((element: ModelElement) => {
     if (element.glyph) {
       const glyph = new Glyph({
         id: element.glyph.id,
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index 658e2a5d..f28cc4bc 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -12,17 +12,17 @@ import {
   lineTypesSelector,
 } from '@/redux/shapes/shapes.selectors';
 import { MapInstance } from '@/types/map';
-import { modelElementsSelector } from '@/redux/modelElements/modelElements.selector';
+import { modelElementsForCurrentModelSelector } from '@/redux/modelElements/modelElements.selector';
 import { currentModelIdSelector } from '@/redux/models/models.selectors';
-import { getModelElements } from '@/redux/modelElements/modelElements.thunks';
+import { getModelElementsForModel } from '@/redux/modelElements/modelElements.thunks';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import CompartmentSquare from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare';
 import CompartmentCircle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle';
 import Glyph from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph';
 import CompartmentPathway from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway';
 import Reaction from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction';
-import { newReactionsDataSelector } from '@/redux/newReactions/newReactions.selectors';
-import { getNewReactions } from '@/redux/newReactions/newReactions.thunks';
+import { newReactionsForCurrentModelSelector } from '@/redux/newReactions/newReactions.selectors';
+import { getNewReactionsForModel } from '@/redux/newReactions/newReactions.thunks';
 import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import {
   getOverlayOrderSelector,
@@ -39,6 +39,7 @@ import MarkerOverlay from '@/components/Map/MapViewer/MapViewerVector/utils/shap
 import processModelElements from '@/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements';
 import useDebouncedValue from '@/utils/useDebouncedValue';
 import { mapBackgroundTypeSelector, mapDataSizeSelector } from '@/redux/map/map.selectors';
+import { ZOOM_RESCALING_FACTOR } from '@/constants/map';
 
 export const useOlMapReactionsLayer = ({
   mapInstance,
@@ -48,8 +49,6 @@ export const useOlMapReactionsLayer = ({
   const dispatch = useAppDispatch();
 
   const currentModelId = useSelector(currentModelIdSelector);
-  const modelElements = useSelector(modelElementsSelector);
-  const modelReactions = useSelector(newReactionsDataSelector);
   const shapes = useSelector(bioShapesSelector);
   const mapSize = useSelector(mapDataSizeSelector);
   const lineTypes = useSelector(lineTypesSelector);
@@ -59,6 +58,8 @@ export const useOlMapReactionsLayer = ({
   const currentMarkers = useAppSelector(markersSufraceOfCurrentMapDataSelector);
   const markersRender = parseSurfaceMarkersToBioEntityRender(currentMarkers);
   const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
+  const reactionsForCurrentModel = useAppSelector(newReactionsForCurrentModelSelector);
+  const modelElementsForCurrentModel = useAppSelector(modelElementsForCurrentModelSelector);
   const debouncedBioEntities = useDebouncedValue(bioEntities, 2000);
 
   const { getOverlayBioEntityColorByAvailableProperties } = useGetOverlayColor();
@@ -67,12 +68,23 @@ export const useOlMapReactionsLayer = ({
 
   const vectorSource = useMemo(() => new VectorSource(), []);
 
+  const mapModelOriginalMaxZoom = mapInstance?.getView().get('originalMaxZoom');
+
+  const isCorrectMapInstanceViewScale = useMemo(() => {
+    return mapSize.maxZoom * ZOOM_RESCALING_FACTOR === mapModelOriginalMaxZoom;
+  }, [mapModelOriginalMaxZoom, mapSize.maxZoom]);
+
   useEffect(() => {
-    if (currentModelId) {
-      dispatch(getModelElements(currentModelId));
-      dispatch(getNewReactions(currentModelId));
+    if (!currentModelId) {
+      return;
+    }
+    if (!modelElementsForCurrentModel) {
+      dispatch(getModelElementsForModel(currentModelId));
     }
-  }, [currentModelId, dispatch]);
+    if (!reactionsForCurrentModel) {
+      dispatch(getNewReactionsForModel(currentModelId));
+    }
+  }, [currentModelId, dispatch, reactionsForCurrentModel, modelElementsForCurrentModel]);
 
   const groupedElementsOverlays = useMemo(() => {
     const elementsBioEntitesOverlay = debouncedBioEntities.filter(
@@ -87,6 +99,9 @@ export const useOlMapReactionsLayer = ({
   }, [bioEntities]);
 
   const linesOverlaysFeatures = useMemo(() => {
+    if (!isCorrectMapInstanceViewScale) {
+      return [];
+    }
     return linesOverlays.map(lineOverlay => {
       return new LineOverlay({
         lineOverlay,
@@ -97,12 +112,16 @@ export const useOlMapReactionsLayer = ({
     });
   }, [
     getOverlayBioEntityColorByAvailableProperties,
+    isCorrectMapInstanceViewScale,
     linesOverlays,
     mapInstance,
     pointToProjection,
   ]);
 
   const markerOverlaysFeatures = useMemo(() => {
+    if (!isCorrectMapInstanceViewScale) {
+      return [];
+    }
     return markersRender.map(marker => {
       return new MarkerOverlay({
         markerOverlay: marker,
@@ -113,13 +132,17 @@ export const useOlMapReactionsLayer = ({
     });
   }, [
     getOverlayBioEntityColorByAvailableProperties,
+    isCorrectMapInstanceViewScale,
     mapInstance,
     markersRender,
     pointToProjection,
   ]);
 
   const reactions = useMemo(() => {
-    return modelReactions.map(reaction => {
+    if (!reactionsForCurrentModel || !isCorrectMapInstanceViewScale) {
+      return [];
+    }
+    return reactionsForCurrentModel.map(reaction => {
       const reactionShapes = shapes && shapes[reaction.sboTerm];
       if (!reactionShapes) {
         return [];
@@ -141,16 +164,25 @@ export const useOlMapReactionsLayer = ({
       });
       return reactionObject.features;
     });
-  }, [arrowTypes, lineTypes, mapInstance, modelReactions, pointToProjection, shapes, vectorSource]);
+  }, [
+    reactionsForCurrentModel,
+    isCorrectMapInstanceViewScale,
+    shapes,
+    lineTypes,
+    arrowTypes,
+    pointToProjection,
+    vectorSource,
+    mapInstance,
+  ]);
 
   const elements: Array<
     MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph
   > = useMemo(() => {
-    if (!modelElements || !shapes) {
+    if (!modelElementsForCurrentModel || !shapes || !isCorrectMapInstanceViewScale) {
       return [];
     }
     return processModelElements(
-      modelElements,
+      modelElementsForCurrentModel,
       shapes,
       lineTypes,
       groupedElementsOverlays,
@@ -163,8 +195,9 @@ export const useOlMapReactionsLayer = ({
       mapSize,
     );
   }, [
-    modelElements,
+    modelElementsForCurrentModel,
     shapes,
+    isCorrectMapInstanceViewScale,
     lineTypes,
     groupedElementsOverlays,
     overlaysOrder,
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.ts
index 77d8be74..a639ebd1 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapView.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapView.ts
@@ -65,16 +65,7 @@ export const useOlMapView = ({ mapInstance }: UseOlMapViewInput): MapConfig['vie
       minZoom: mapSize.minZoom * ZOOM_RESCALING_FACTOR,
       extent,
     }),
-    [
-      center,
-      mapInitialPosition.z,
-      mapSize.width,
-      mapSize.tileSize,
-      mapSize.height,
-      mapSize.maxZoom,
-      mapSize.minZoom,
-      extent,
-    ],
+    [center, mapInitialPosition.z, mapSize, extent],
   );
 
   const view = useMemo(() => new View(viewConfig), [viewConfig]);
diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts
index 5346ef4d..c0686ae3 100644
--- a/src/redux/apiPath.ts
+++ b/src/redux/apiPath.ts
@@ -44,7 +44,7 @@ export const apiPath = {
   getChemicalsStringWithColumnsTarget: (columns: string, target: string): string =>
     `projects/${PROJECT_ID}/chemicals:search?columns=${columns}&target=${target}`,
   getModelsString: (): string => `projects/${PROJECT_ID}/models/`,
-  getModelElements: (modelId: number): string =>
+  getModelElementsForModel: (modelId: number): string =>
     `projects/${PROJECT_ID}/maps/${modelId}/bioEntities/elements/?size=10000`,
   getShapes: (): string => `projects/${PROJECT_ID}/shapes/`,
   getLineTypes: (): string => `projects/${PROJECT_ID}/lineTypes/`,
@@ -60,8 +60,8 @@ export const apiPath = {
     `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}/lines/`,
   getGlyphImage: (glyphId: number): string =>
     `projects/${PROJECT_ID}/glyphs/${glyphId}/fileContent`,
-  getNewReactions: (modelId: number): string =>
-    `projects/${PROJECT_ID}/maps/${modelId}/bioEntities/reactions/?size=2000`,
+  getNewReactionsForModel: (modelId: number): string =>
+    `projects/${PROJECT_ID}/maps/${modelId}/bioEntities/reactions/?size=10000`,
   getNewReaction: (modelId: number, reactionId: number): string =>
     `projects/${PROJECT_ID}/maps/${modelId}/bioEntities/reactions/${reactionId}`,
   getChemicalsStringWithQuery: (searchQuery: string): string =>
diff --git a/src/redux/modelElements/modelElements.mock.ts b/src/redux/modelElements/modelElements.mock.ts
index 5ad63a02..0ee98c43 100644
--- a/src/redux/modelElements/modelElements.mock.ts
+++ b/src/redux/modelElements/modelElements.mock.ts
@@ -2,7 +2,7 @@ import { DEFAULT_ERROR } from '@/constants/errors';
 import { ModelElementsState } from '@/redux/modelElements/modelElements.types';
 
 export const MODEL_ELEMENTS_INITIAL_STATE_MOCK: ModelElementsState = {
-  data: null,
+  data: {},
   loading: 'idle',
   error: DEFAULT_ERROR,
 };
diff --git a/src/redux/modelElements/modelElements.reducers.test.ts b/src/redux/modelElements/modelElements.reducers.test.ts
index d8960722..3554f048 100644
--- a/src/redux/modelElements/modelElements.reducers.test.ts
+++ b/src/redux/modelElements/modelElements.reducers.test.ts
@@ -9,13 +9,13 @@ import { HttpStatusCode } from 'axios';
 import { unwrapResult } from '@reduxjs/toolkit';
 import { ModelElementsState } from '@/redux/modelElements/modelElements.types';
 import modelElementsReducer from '@/redux/modelElements/modelElements.slice';
-import { getModelElements } from '@/redux/modelElements/modelElements.thunks';
+import { getModelElementsForModel } from '@/redux/modelElements/modelElements.thunks';
 import { modelElementsFixture } from '@/models/fixtures/modelElementsFixture';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
 
 const INITIAL_STATE: ModelElementsState = {
-  data: null,
+  data: {},
   loading: 'idle',
   error: { name: '', message: '' },
 };
@@ -32,51 +32,51 @@ describe('model elements reducer', () => {
     expect(modelElementsReducer(undefined, action)).toEqual(INITIAL_STATE);
   });
 
-  it('should update store after successful getModelElements query', async () => {
+  it('should update store after successful getModelElementsForModel query', async () => {
     mockedAxiosClient
-      .onGet(apiPath.getModelElements(0))
+      .onGet(apiPath.getModelElementsForModel(0))
       .reply(HttpStatusCode.Ok, modelElementsFixture);
 
-    const { type } = await store.dispatch(getModelElements(0));
+    const { type } = await store.dispatch(getModelElementsForModel(0));
     const { data, loading, error } = store.getState().modelElements;
 
-    expect(type).toBe('vectorMap/getModelElements/fulfilled');
+    expect(type).toBe('vectorMap/getModelElementsForModel/fulfilled');
     expect(loading).toEqual('succeeded');
     expect(error).toEqual({ message: '', name: '' });
-    expect(data).toEqual(modelElementsFixture);
+    expect(data).toEqual({ 0: modelElementsFixture.content });
   });
 
-  it('should update store after failed getModelElements query', async () => {
-    mockedAxiosClient.onGet(apiPath.getModelElements(0)).reply(HttpStatusCode.NotFound, []);
+  it('should update store after failed getModelElementsForModel query', async () => {
+    mockedAxiosClient.onGet(apiPath.getModelElementsForModel(0)).reply(HttpStatusCode.NotFound, []);
 
-    const action = await store.dispatch(getModelElements(0));
+    const action = await store.dispatch(getModelElementsForModel(0));
     const { data, loading, error } = store.getState().modelElements;
 
-    expect(action.type).toBe('vectorMap/getModelElements/rejected');
+    expect(action.type).toBe('vectorMap/getModelElementsForModel/rejected');
     expect(() => unwrapResult(action)).toThrow(
       "Failed to fetch model elements: The page you're looking for doesn't exist. Please verify the URL and try again.",
     );
     expect(loading).toEqual('failed');
     expect(error).toEqual({ message: '', name: '' });
-    expect(data).toEqual(null);
+    expect(data).toEqual({});
   });
 
-  it('should update store on loading getModelElements query', async () => {
+  it('should update store on loading getModelElementsForModel query', async () => {
     mockedAxiosClient
-      .onGet(apiPath.getModelElements(0))
+      .onGet(apiPath.getModelElementsForModel(0))
       .reply(HttpStatusCode.Ok, modelElementsFixture);
 
-    const modelElementsPromise = store.dispatch(getModelElements(0));
+    const modelElementsPromise = store.dispatch(getModelElementsForModel(0));
 
     const { data, loading } = store.getState().modelElements;
-    expect(data).toEqual(null);
+    expect(data).toEqual({});
     expect(loading).toEqual('pending');
 
     modelElementsPromise.then(() => {
       const { data: dataPromiseFulfilled, loading: promiseFulfilled } =
         store.getState().modelElements;
 
-      expect(dataPromiseFulfilled).toEqual(modelElementsFixture);
+      expect(dataPromiseFulfilled).toEqual({ 0: modelElementsFixture.content });
       expect(promiseFulfilled).toEqual('succeeded');
     });
   });
diff --git a/src/redux/modelElements/modelElements.reducers.ts b/src/redux/modelElements/modelElements.reducers.ts
index fda618df..15a40c42 100644
--- a/src/redux/modelElements/modelElements.reducers.ts
+++ b/src/redux/modelElements/modelElements.reducers.ts
@@ -1,18 +1,25 @@
 import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
-import { getModelElements } from '@/redux/modelElements/modelElements.thunks';
+import { getModelElementsForModel } from '@/redux/modelElements/modelElements.thunks';
 import { ModelElementsState } from '@/redux/modelElements/modelElements.types';
 
 export const getModelElementsReducer = (
   builder: ActionReducerMapBuilder<ModelElementsState>,
 ): void => {
-  builder.addCase(getModelElements.pending, state => {
+  builder.addCase(getModelElementsForModel.pending, state => {
     state.loading = 'pending';
   });
-  builder.addCase(getModelElements.fulfilled, (state, action) => {
-    state.data = action.payload || null;
+  builder.addCase(getModelElementsForModel.fulfilled, (state, action) => {
+    const modelId = action.meta.arg;
+    if (state.data) {
+      state.data[modelId] = action.payload || [];
+    } else {
+      state.data = {
+        [modelId]: action.payload || [],
+      };
+    }
     state.loading = 'succeeded';
   });
-  builder.addCase(getModelElements.rejected, state => {
+  builder.addCase(getModelElementsForModel.rejected, state => {
     state.loading = 'failed';
   });
 };
diff --git a/src/redux/modelElements/modelElements.selector.ts b/src/redux/modelElements/modelElements.selector.ts
index 3a9d8b54..f9a654bf 100644
--- a/src/redux/modelElements/modelElements.selector.ts
+++ b/src/redux/modelElements/modelElements.selector.ts
@@ -1,12 +1,21 @@
 import { createSelector } from '@reduxjs/toolkit';
 import { rootSelector } from '@/redux/root/root.selectors';
+import { currentModelIdSelector } from '@/redux/models/models.selectors';
 
-export const modelElementsSelector = createSelector(
-  rootSelector,
-  state => state.modelElements.data,
-);
+export const modelElementsSelector = createSelector(rootSelector, state => state.modelElements);
 
 export const modelElementsLoadingSelector = createSelector(
   rootSelector,
   state => state.modelElements.loading,
 );
+
+export const modelElementsDataSelector = createSelector(
+  modelElementsSelector,
+  modelElements => modelElements.data || {},
+);
+
+export const modelElementsForCurrentModelSelector = createSelector(
+  modelElementsDataSelector,
+  currentModelIdSelector,
+  (data, currentModelId) => data[currentModelId],
+);
diff --git a/src/redux/modelElements/modelElements.slice.ts b/src/redux/modelElements/modelElements.slice.ts
index b9e7f411..56b5d77a 100644
--- a/src/redux/modelElements/modelElements.slice.ts
+++ b/src/redux/modelElements/modelElements.slice.ts
@@ -3,7 +3,7 @@ import { getModelElementsReducer } from '@/redux/modelElements/modelElements.red
 import { ModelElementsState } from '@/redux/modelElements/modelElements.types';
 
 const initialState: ModelElementsState = {
-  data: null,
+  data: {},
   loading: 'idle',
   error: { name: '', message: '' },
 };
diff --git a/src/redux/modelElements/modelElements.thunks.test.ts b/src/redux/modelElements/modelElements.thunks.test.ts
index c5611de7..0ba825c9 100644
--- a/src/redux/modelElements/modelElements.thunks.test.ts
+++ b/src/redux/modelElements/modelElements.thunks.test.ts
@@ -8,7 +8,7 @@ import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
 import { HttpStatusCode } from 'axios';
 import { ModelElementsState } from '@/redux/modelElements/modelElements.types';
 import modelElementsReducer from '@/redux/modelElements/modelElements.slice';
-import { getModelElements } from '@/redux/modelElements/modelElements.thunks';
+import { getModelElementsForModel } from '@/redux/modelElements/modelElements.thunks';
 import { modelElementsFixture } from '@/models/fixtures/modelElementsFixture';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
@@ -19,22 +19,22 @@ describe('model elements thunks', () => {
     store = createStoreInstanceUsingSliceReducer('modelElements', modelElementsReducer);
   });
 
-  describe('getModelElements', () => {
+  describe('getModelElementsForModel', () => {
     it('should return data when data response from API is valid', async () => {
       mockedAxiosClient
-        .onGet(apiPath.getModelElements(0))
+        .onGet(apiPath.getModelElementsForModel(0))
         .reply(HttpStatusCode.Ok, modelElementsFixture);
 
-      const { payload } = await store.dispatch(getModelElements(0));
-      expect(payload).toEqual(modelElementsFixture);
+      const { payload } = await store.dispatch(getModelElementsForModel(0));
+      expect(payload).toEqual(modelElementsFixture.content);
     });
 
     it('should return undefined when data response from API is not valid ', async () => {
       mockedAxiosClient
-        .onGet(apiPath.getModelElements(0))
+        .onGet(apiPath.getModelElementsForModel(0))
         .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' });
 
-      const { payload } = await store.dispatch(getModelElements(0));
+      const { payload } = await store.dispatch(getModelElementsForModel(0));
       expect(payload).toEqual(undefined);
     });
   });
diff --git a/src/redux/modelElements/modelElements.thunks.ts b/src/redux/modelElements/modelElements.thunks.ts
index 2c7e2f25..210b6560 100644
--- a/src/redux/modelElements/modelElements.thunks.ts
+++ b/src/redux/modelElements/modelElements.thunks.ts
@@ -4,25 +4,23 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
 import { ThunkConfig } from '@/types/store';
 import { getError } from '@/utils/error-report/getError';
 import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
-import { ModelElements } from '@/types/models';
+import { ModelElement, ModelElements } from '@/types/models';
 import { MODEL_ELEMENTS_FETCHING_ERROR_PREFIX } from '@/redux/modelElements/modelElements.constants';
 import { modelElementSchema } from '@/models/modelElementSchema';
 import { pageableSchema } from '@/models/pageableSchema';
 
-export const getModelElements = createAsyncThunk<ModelElements | undefined, number, ThunkConfig>(
-  'vectorMap/getModelElements',
-  async (modelId: number) => {
-    try {
-      const response = await axiosInstanceNewAPI.get<ModelElements>(
-        apiPath.getModelElements(modelId),
-      );
-      const isDataValid = validateDataUsingZodSchema(
-        response.data,
-        pageableSchema(modelElementSchema),
-      );
-      return isDataValid ? response.data : undefined;
-    } catch (error) {
-      return Promise.reject(getError({ error, prefix: MODEL_ELEMENTS_FETCHING_ERROR_PREFIX }));
-    }
-  },
-);
+export const getModelElementsForModel = createAsyncThunk<
+  Array<ModelElement> | undefined,
+  number,
+  ThunkConfig
+>('vectorMap/getModelElementsForModel', async (modelId: number) => {
+  try {
+    const { data } = await axiosInstanceNewAPI.get<ModelElements>(
+      apiPath.getModelElementsForModel(modelId),
+    );
+    const isDataValid = validateDataUsingZodSchema(data, pageableSchema(modelElementSchema));
+    return isDataValid ? data.content : undefined;
+  } catch (error) {
+    return Promise.reject(getError({ error, prefix: MODEL_ELEMENTS_FETCHING_ERROR_PREFIX }));
+  }
+});
diff --git a/src/redux/modelElements/modelElements.types.ts b/src/redux/modelElements/modelElements.types.ts
index 0dfdb426..808f3ace 100644
--- a/src/redux/modelElements/modelElements.types.ts
+++ b/src/redux/modelElements/modelElements.types.ts
@@ -1,4 +1,6 @@
 import { FetchDataState } from '@/types/fetchDataState';
-import { ModelElements } from '@/types/models';
+import { ModelElement } from '@/types/models';
 
-export type ModelElementsState = FetchDataState<ModelElements, null>;
+type ModelElementsRecord = Record<number, Array<ModelElement>>;
+
+export type ModelElementsState = FetchDataState<ModelElementsRecord>;
diff --git a/src/redux/newReactions/newReactions.constants.ts b/src/redux/newReactions/newReactions.constants.ts
index 80ea28cc..e30ac5fa 100644
--- a/src/redux/newReactions/newReactions.constants.ts
+++ b/src/redux/newReactions/newReactions.constants.ts
@@ -2,7 +2,7 @@ import { DEFAULT_ERROR } from '@/constants/errors';
 import { NewReactionsState } from '@/redux/newReactions/newReactions.types';
 
 export const NEW_REACTIONS_INITIAL_STATE: NewReactionsState = {
-  data: [],
+  data: {},
   loading: 'idle',
   error: DEFAULT_ERROR,
 };
diff --git a/src/redux/newReactions/newReactions.mock.ts b/src/redux/newReactions/newReactions.mock.ts
index 3847aed6..f9a878ec 100644
--- a/src/redux/newReactions/newReactions.mock.ts
+++ b/src/redux/newReactions/newReactions.mock.ts
@@ -2,7 +2,7 @@ import { DEFAULT_ERROR } from '@/constants/errors';
 import { NewReactionsState } from '@/redux/newReactions/newReactions.types';
 
 export const NEW_REACTIONS_INITIAL_STATE_MOCK: NewReactionsState = {
-  data: [],
+  data: {},
   loading: 'idle',
   error: DEFAULT_ERROR,
 };
diff --git a/src/redux/newReactions/newReactions.reducers.test.ts b/src/redux/newReactions/newReactions.reducers.test.ts
index 21f8d668..206d34a7 100644
--- a/src/redux/newReactions/newReactions.reducers.test.ts
+++ b/src/redux/newReactions/newReactions.reducers.test.ts
@@ -10,7 +10,7 @@ import { unwrapResult } from '@reduxjs/toolkit';
 import newReactionsReducer from '@/redux/newReactions/newReactions.slice';
 import { NewReactionsState } from '@/redux/newReactions/newReactions.types';
 import { NEW_REACTIONS_INITIAL_STATE_MOCK } from '@/redux/newReactions/newReactions.mock';
-import { getNewReactions } from '@/redux/newReactions/newReactions.thunks';
+import { getNewReactionsForModel } from '@/redux/newReactions/newReactions.thunks';
 import { newReactionsFixture } from '@/models/fixtures/newReactionsFixture';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
@@ -29,50 +29,50 @@ describe('newReactions reducer', () => {
     expect(newReactionsReducer(undefined, action)).toEqual(INITIAL_STATE);
   });
 
-  it('should update store after successful getNewReactions query', async () => {
+  it('should update store after successful getNewReactionsForModel query', async () => {
     mockedAxiosClient
-      .onGet(apiPath.getNewReactions(1))
+      .onGet(apiPath.getNewReactionsForModel(1))
       .reply(HttpStatusCode.Ok, newReactionsFixture);
 
-    const { type } = await store.dispatch(getNewReactions(1));
+    const { type } = await store.dispatch(getNewReactionsForModel(1));
     const { data, loading, error } = store.getState().newReactions;
-    expect(type).toBe('newReactions/getNewReactions/fulfilled');
+    expect(type).toBe('newReactions/getNewReactionsForModel/fulfilled');
     expect(loading).toEqual('succeeded');
     expect(error).toEqual({ message: '', name: '' });
-    expect(data).toEqual(newReactionsFixture.content);
+    expect(data).toEqual({ 1: newReactionsFixture.content });
   });
 
-  it('should update store after failed getNewReactions query', async () => {
-    mockedAxiosClient.onGet(apiPath.getNewReactions(1)).reply(HttpStatusCode.NotFound, []);
+  it('should update store after failed getNewReactionsForModel query', async () => {
+    mockedAxiosClient.onGet(apiPath.getNewReactionsForModel(1)).reply(HttpStatusCode.NotFound, []);
 
-    const action = await store.dispatch(getNewReactions(1));
+    const action = await store.dispatch(getNewReactionsForModel(1));
     const { data, loading, error } = store.getState().newReactions;
 
-    expect(action.type).toBe('newReactions/getNewReactions/rejected');
+    expect(action.type).toBe('newReactions/getNewReactionsForModel/rejected');
     expect(() => unwrapResult(action)).toThrow(
       "Failed to fetch new reactions: The page you're looking for doesn't exist. Please verify the URL and try again.",
     );
     expect(loading).toEqual('failed');
     expect(error).toEqual({ message: '', name: '' });
-    expect(data).toEqual([]);
+    expect(data).toEqual({});
   });
 
-  it('should update store on loading getNewReactions query', async () => {
+  it('should update store on loading getNewReactionsForModel query', async () => {
     mockedAxiosClient
-      .onGet(apiPath.getNewReactions(1))
+      .onGet(apiPath.getNewReactionsForModel(1))
       .reply(HttpStatusCode.Ok, newReactionsFixture);
 
-    const newReactionsPromise = store.dispatch(getNewReactions(1));
+    const newReactionsPromise = store.dispatch(getNewReactionsForModel(1));
 
     const { data, loading } = store.getState().newReactions;
-    expect(data).toEqual([]);
+    expect(data).toEqual({});
     expect(loading).toEqual('pending');
 
     newReactionsPromise.then(() => {
       const { data: dataPromiseFulfilled, loading: promiseFulfilled } =
         store.getState().newReactions;
 
-      expect(dataPromiseFulfilled).toEqual(newReactionsFixture.content);
+      expect(dataPromiseFulfilled).toEqual({ 1: newReactionsFixture.content });
       expect(promiseFulfilled).toEqual('succeeded');
     });
   });
diff --git a/src/redux/newReactions/newReactions.reducers.ts b/src/redux/newReactions/newReactions.reducers.ts
index 306b306e..70454483 100644
--- a/src/redux/newReactions/newReactions.reducers.ts
+++ b/src/redux/newReactions/newReactions.reducers.ts
@@ -1,18 +1,25 @@
 import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
-import { getNewReactions } from '@/redux/newReactions/newReactions.thunks';
+import { getNewReactionsForModel } from '@/redux/newReactions/newReactions.thunks';
 import { NewReactionsState } from '@/redux/newReactions/newReactions.types';
 
 export const getNewReactionsReducer = (
   builder: ActionReducerMapBuilder<NewReactionsState>,
 ): void => {
-  builder.addCase(getNewReactions.pending, state => {
+  builder.addCase(getNewReactionsForModel.pending, state => {
     state.loading = 'pending';
   });
-  builder.addCase(getNewReactions.fulfilled, (state, action) => {
-    state.data = action.payload || [];
+  builder.addCase(getNewReactionsForModel.fulfilled, (state, action) => {
+    const modelId = action.meta.arg;
+    if (state.data) {
+      state.data[modelId] = action.payload || [];
+    } else {
+      state.data = {
+        [modelId]: action.payload || [],
+      };
+    }
     state.loading = 'succeeded';
   });
-  builder.addCase(getNewReactions.rejected, state => {
+  builder.addCase(getNewReactionsForModel.rejected, state => {
     state.loading = 'failed';
   });
 };
diff --git a/src/redux/newReactions/newReactions.selectors.ts b/src/redux/newReactions/newReactions.selectors.ts
index 146bbc85..eb461818 100644
--- a/src/redux/newReactions/newReactions.selectors.ts
+++ b/src/redux/newReactions/newReactions.selectors.ts
@@ -1,4 +1,5 @@
 import { createSelector } from '@reduxjs/toolkit';
+import { currentModelIdSelector } from '@/redux/models/models.selectors';
 import { rootSelector } from '../root/root.selectors';
 
 export const newReactionsSelector = createSelector(rootSelector, state => state.newReactions);
@@ -10,5 +11,11 @@ export const newReactionsLoadingSelector = createSelector(
 
 export const newReactionsDataSelector = createSelector(
   newReactionsSelector,
-  reactions => reactions.data || [],
+  reactions => reactions.data || {},
+);
+
+export const newReactionsForCurrentModelSelector = createSelector(
+  newReactionsDataSelector,
+  currentModelIdSelector,
+  (data, currentModelId) => data[currentModelId],
 );
diff --git a/src/redux/newReactions/newReactions.thunks.test.ts b/src/redux/newReactions/newReactions.thunks.test.ts
index afd14d77..3bcc5a9b 100644
--- a/src/redux/newReactions/newReactions.thunks.test.ts
+++ b/src/redux/newReactions/newReactions.thunks.test.ts
@@ -9,7 +9,7 @@ import { HttpStatusCode } from 'axios';
 import newReactionsReducer from '@/redux/newReactions/newReactions.slice';
 import { NewReactionsState } from '@/redux/newReactions/newReactions.types';
 import { newReactionsFixture } from '@/models/fixtures/newReactionsFixture';
-import { getNewReactions } from '@/redux/newReactions/newReactions.thunks';
+import { getNewReactionsForModel } from '@/redux/newReactions/newReactions.thunks';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
 
@@ -22,19 +22,19 @@ describe('newReactions thunks', () => {
   describe('getReactions', () => {
     it('should return data when data response from API is valid', async () => {
       mockedAxiosClient
-        .onGet(apiPath.getNewReactions(1))
+        .onGet(apiPath.getNewReactionsForModel(1))
         .reply(HttpStatusCode.Ok, newReactionsFixture);
 
-      const { payload } = await store.dispatch(getNewReactions(1));
+      const { payload } = await store.dispatch(getNewReactionsForModel(1));
       expect(payload).toEqual(newReactionsFixture.content);
     });
 
     it('should return undefined when data response from API is not valid ', async () => {
       mockedAxiosClient
-        .onGet(apiPath.getNewReactions(1))
+        .onGet(apiPath.getNewReactionsForModel(1))
         .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' });
 
-      const { payload } = await store.dispatch(getNewReactions(1));
+      const { payload } = await store.dispatch(getNewReactionsForModel(1));
       expect(payload).toEqual(undefined);
     });
   });
diff --git a/src/redux/newReactions/newReactions.thunks.ts b/src/redux/newReactions/newReactions.thunks.ts
index 7e7081a8..2efd3f06 100644
--- a/src/redux/newReactions/newReactions.thunks.ts
+++ b/src/redux/newReactions/newReactions.thunks.ts
@@ -9,13 +9,15 @@ import { newReactionSchema } from '@/models/newReactionSchema';
 import { pageableSchema } from '@/models/pageableSchema';
 import { NEW_REACTIONS_FETCHING_ERROR_PREFIX } from '@/redux/newReactions/newReactions.constants';
 
-export const getNewReactions = createAsyncThunk<
+export const getNewReactionsForModel = createAsyncThunk<
   Array<NewReaction> | undefined,
   number,
   ThunkConfig
->('newReactions/getNewReactions', async (modelId: number) => {
+>('newReactions/getNewReactionsForModel', async (modelId: number) => {
   try {
-    const { data } = await axiosInstanceNewAPI.get<NewReactions>(apiPath.getNewReactions(modelId));
+    const { data } = await axiosInstanceNewAPI.get<NewReactions>(
+      apiPath.getNewReactionsForModel(modelId),
+    );
     const isDataValid = validateDataUsingZodSchema(data, pageableSchema(newReactionSchema));
     return isDataValid ? data.content : undefined;
   } catch (error) {
diff --git a/src/redux/newReactions/newReactions.types.ts b/src/redux/newReactions/newReactions.types.ts
index 142906d7..7b36d1c5 100644
--- a/src/redux/newReactions/newReactions.types.ts
+++ b/src/redux/newReactions/newReactions.types.ts
@@ -1,4 +1,6 @@
 import { FetchDataState } from '@/types/fetchDataState';
 import { NewReaction } from '@/types/models';
 
-export type NewReactionsState = FetchDataState<NewReaction[]>;
+type NewReactionsRecord = Record<number, NewReaction[]>;
+
+export type NewReactionsState = FetchDataState<NewReactionsRecord>;
-- 
GitLab


From dfe46d3b18422df76bf04ced265a49de6edf5ea8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 28 Nov 2024 13:13:04 +0100
Subject: [PATCH 26/29] refactor(vector-map): modify layers redux to store
 layers of multiple models

---
 .../LayersDrawer/LayersDrawer.component.tsx   | 23 +++--
 .../useOlMapAdditionalLayers.ts               | 35 ++++---
 src/redux/layers/layers.mock.ts               | 12 ++-
 src/redux/layers/layers.reducers.test.ts      | 20 ++--
 src/redux/layers/layers.reducers.ts           | 54 +++++++----
 src/redux/layers/layers.selectors.ts          | 26 ++++--
 src/redux/layers/layers.slice.ts              |  7 +-
 src/redux/layers/layers.thunks.test.ts        |  8 +-
 src/redux/layers/layers.thunks.ts             | 93 ++++++++++---------
 src/redux/layers/layers.types.ts              |  4 +-
 10 files changed, 176 insertions(+), 106 deletions(-)

diff --git a/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx b/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx
index 7e614298..6ee65f6b 100644
--- a/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx
+++ b/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx
@@ -1,26 +1,37 @@
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
 import { DrawerHeading } from '@/shared/DrawerHeading';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
-import { layersSelector, layersVisibilitySelector } from '@/redux/layers/layers.selectors';
+import {
+  layersForCurrentModelSelector,
+  layersVisibilityForCurrentModelSelector,
+} from '@/redux/layers/layers.selectors';
 import { Switch } from '@/shared/Switch';
 import { setLayerVisibility } from '@/redux/layers/layers.slice';
+import { currentModelIdSelector } from '@/redux/models/models.selectors';
 
 export const LayersDrawer = (): JSX.Element => {
-  const layers = useAppSelector(layersSelector);
-  const layersVisibility = useAppSelector(layersVisibilitySelector);
+  const layersForCurrentModel = useAppSelector(layersForCurrentModelSelector);
+  const layersVisibilityForCurrentModel = useAppSelector(layersVisibilityForCurrentModelSelector);
+  const currentModelId = useAppSelector(currentModelIdSelector);
   const dispatch = useAppDispatch();
 
   return (
     <div data-testid="layers-drawer" className="h-full max-h-full">
       <DrawerHeading title="Layers" />
       <div className="flex h-[calc(100%-93px)] max-h-[calc(100%-93px)] flex-col overflow-y-auto px-6">
-        {layers.map(layer => (
+        {layersForCurrentModel.map(layer => (
           <div key={layer.details.id} className="flex items-center justify-between border-b p-4">
             <h1>{layer.details.name}</h1>
             <Switch
-              isChecked={layersVisibility[layer.details.layerId]}
+              isChecked={layersVisibilityForCurrentModel[layer.details.layerId]}
               onToggle={value =>
-                dispatch(setLayerVisibility({ visible: value, layerId: layer.details.layerId }))
+                dispatch(
+                  setLayerVisibility({
+                    modelId: currentModelId,
+                    visible: value,
+                    layerId: layer.details.layerId,
+                  }),
+                )
               }
             />
           </div>
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
index 98a4276a..f56532ef 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
@@ -6,14 +6,19 @@ import { useEffect, useMemo } from 'react';
 import { useSelector } from 'react-redux';
 import { currentModelIdSelector } from '@/redux/models/models.selectors';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
-import { getLayers } from '@/redux/layers/layers.thunks';
-import { layersSelector, layersVisibilitySelector } from '@/redux/layers/layers.selectors';
+import { getLayersForModel } from '@/redux/layers/layers.thunks';
+import {
+  layersForCurrentModelSelector,
+  layersLoadingSelector,
+  layersVisibilityForCurrentModelSelector,
+} from '@/redux/layers/layers.selectors';
 import { usePointToProjection } from '@/utils/map/usePointToProjection';
 import { MapInstance } from '@/types/map';
 import { LineString, MultiPolygon, Point } from 'ol/geom';
 import Polygon from 'ol/geom/Polygon';
 import Layer from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer';
 import { arrowTypesSelector, lineTypesSelector } from '@/redux/shapes/shapes.selectors';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
 
 export const useOlMapAdditionalLayers = (
   mapInstance: MapInstance,
@@ -24,18 +29,26 @@ export const useOlMapAdditionalLayers = (
 > => {
   const dispatch = useAppDispatch();
   const currentModelId = useSelector(currentModelIdSelector);
-  const mapLayers = useSelector(layersSelector);
-  const layersVisibility = useSelector(layersVisibilitySelector);
+
+  const layersForCurrentModel = useAppSelector(layersForCurrentModelSelector);
+  const layersLoading = useAppSelector(layersLoadingSelector);
+  const layersVisibilityForCurrentModel = useAppSelector(layersVisibilityForCurrentModelSelector);
+
   const lineTypes = useSelector(lineTypesSelector);
   const arrowTypes = useSelector(arrowTypesSelector);
   const pointToProjection = usePointToProjection();
 
   useEffect(() => {
-    dispatch(getLayers(currentModelId));
-  }, [currentModelId, dispatch]);
+    if (!currentModelId) {
+      return;
+    }
+    if (layersLoading !== 'succeeded') {
+      dispatch(getLayersForModel(currentModelId));
+    }
+  }, [currentModelId, dispatch, layersLoading]);
 
   const vectorLayers = useMemo(() => {
-    return mapLayers.map(layer => {
+    return layersForCurrentModel.map(layer => {
       const additionalLayer = new Layer({
         texts: layer.texts,
         rects: layer.rects,
@@ -50,16 +63,16 @@ export const useOlMapAdditionalLayers = (
       });
       return additionalLayer.vectorLayer;
     });
-  }, [arrowTypes, lineTypes, mapInstance, mapLayers, pointToProjection]);
+  }, [arrowTypes, lineTypes, mapInstance, layersForCurrentModel, pointToProjection]);
 
   useEffect(() => {
     vectorLayers.forEach(layer => {
       const layerId = layer.get('id');
-      if (layerId && layersVisibility[layerId] !== undefined) {
-        layer.setVisible(layersVisibility[layerId]);
+      if (layerId && layersVisibilityForCurrentModel[layerId] !== undefined) {
+        layer.setVisible(layersVisibilityForCurrentModel[layerId]);
       }
     });
-  }, [layersVisibility, vectorLayers]);
+  }, [layersVisibilityForCurrentModel, vectorLayers]);
 
   return vectorLayers;
 };
diff --git a/src/redux/layers/layers.mock.ts b/src/redux/layers/layers.mock.ts
index 9ec2ce4c..38e72675 100644
--- a/src/redux/layers/layers.mock.ts
+++ b/src/redux/layers/layers.mock.ts
@@ -1,7 +1,10 @@
-import { LayersState } from '@/redux/layers/layers.types';
+import { LayersState, LayersVisibilitiesState } from '@/redux/layers/layers.types';
 import { DEFAULT_ERROR } from '@/constants/errors';
+import { FetchDataState } from '@/types/fetchDataState';
 
-export const LAYERS_STATE_INITIAL_MOCK: LayersState = {
+export const LAYERS_STATE_INITIAL_MOCK: LayersState = {};
+
+export const LAYERS_STATE_INITIAL_LAYER_MOCK: FetchDataState<LayersVisibilitiesState> = {
   data: {
     layers: [],
     layersVisibility: {},
@@ -9,3 +12,8 @@ export const LAYERS_STATE_INITIAL_MOCK: LayersState = {
   loading: 'idle',
   error: DEFAULT_ERROR,
 };
+
+export const LAYER_STATE_DEFAULT_DATA = {
+  layers: [],
+  layersVisibility: {},
+};
diff --git a/src/redux/layers/layers.reducers.test.ts b/src/redux/layers/layers.reducers.test.ts
index 827c1dff..ffc0d923 100644
--- a/src/redux/layers/layers.reducers.test.ts
+++ b/src/redux/layers/layers.reducers.test.ts
@@ -7,8 +7,8 @@ import {
 import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
 import { HttpStatusCode } from 'axios';
 import { unwrapResult } from '@reduxjs/toolkit';
-import { LAYERS_STATE_INITIAL_MOCK } from '@/redux/layers/layers.mock';
-import { getLayers } from '@/redux/layers/layers.thunks';
+import { LAYER_STATE_DEFAULT_DATA, LAYERS_STATE_INITIAL_MOCK } from '@/redux/layers/layers.mock';
+import { getLayersForModel } from '@/redux/layers/layers.thunks';
 import { layersFixture } from '@/models/fixtures/layersFixture';
 import { layerTextsFixture } from '@/models/fixtures/layerTextsFixture';
 import { layerRectsFixture } from '@/models/fixtures/layerRectsFixture';
@@ -48,8 +48,8 @@ describe('layers reducer', () => {
       .onGet(apiPath.getLayerLines(1, layersFixture.content[0].id))
       .reply(HttpStatusCode.Ok, layerLinesFixture);
 
-    const { type } = await store.dispatch(getLayers(1));
-    const { data, loading, error } = store.getState().layers;
+    const { type } = await store.dispatch(getLayersForModel(1));
+    const { data, loading, error } = store.getState().layers[1];
     expect(type).toBe('vectorMap/getLayers/fulfilled');
     expect(loading).toEqual('succeeded');
     expect(error).toEqual({ message: '', name: '' });
@@ -72,8 +72,8 @@ describe('layers reducer', () => {
   it('should update store after failed getLayers query', async () => {
     mockedAxiosClient.onGet(apiPath.getLayers(1)).reply(HttpStatusCode.NotFound, []);
 
-    const action = await store.dispatch(getLayers(1));
-    const { data, loading, error } = store.getState().layers;
+    const action = await store.dispatch(getLayersForModel(1));
+    const { data, loading, error } = store.getState().layers[1];
 
     expect(action.type).toBe('vectorMap/getLayers/rejected');
     expect(() => unwrapResult(action)).toThrow(
@@ -99,14 +99,14 @@ describe('layers reducer', () => {
       .onGet(apiPath.getLayerLines(1, layersFixture.content[0].id))
       .reply(HttpStatusCode.Ok, layerLinesFixture);
 
-    const layersPromise = store.dispatch(getLayers(1));
+    const layersPromise = store.dispatch(getLayersForModel(1));
 
-    const { data, loading } = store.getState().layers;
-    expect(data).toEqual({ layers: [], layersVisibility: {} });
+    const { data, loading } = store.getState().layers[1];
+    expect(data).toEqual(LAYER_STATE_DEFAULT_DATA);
     expect(loading).toEqual('pending');
 
     layersPromise.then(() => {
-      const { data: dataPromiseFulfilled, loading: promiseFulfilled } = store.getState().layers;
+      const { data: dataPromiseFulfilled, loading: promiseFulfilled } = store.getState().layers[1];
 
       expect(dataPromiseFulfilled).toEqual({
         layers: [
diff --git a/src/redux/layers/layers.reducers.ts b/src/redux/layers/layers.reducers.ts
index ed75a687..0cc1ad73 100644
--- a/src/redux/layers/layers.reducers.ts
+++ b/src/redux/layers/layers.reducers.ts
@@ -1,30 +1,52 @@
 /* eslint-disable no-magic-numbers */
 import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit';
-import { getLayers } from '@/redux/layers/layers.thunks';
+import { getLayersForModel } from '@/redux/layers/layers.thunks';
 import { LayersState } from '@/redux/layers/layers.types';
+import {
+  LAYER_STATE_DEFAULT_DATA,
+  LAYERS_STATE_INITIAL_LAYER_MOCK,
+} from '@/redux/layers/layers.mock';
+import { DEFAULT_ERROR } from '@/constants/errors';
 
-export const getLayersReducer = (builder: ActionReducerMapBuilder<LayersState>): void => {
-  builder.addCase(getLayers.pending, state => {
-    state.loading = 'pending';
+export const getLayersForModelReducer = (builder: ActionReducerMapBuilder<LayersState>): void => {
+  builder.addCase(getLayersForModel.pending, (state, action) => {
+    const modelId = action.meta.arg;
+    if (state[modelId]) {
+      state[modelId].loading = 'pending';
+    } else {
+      state[modelId] = { ...LAYERS_STATE_INITIAL_LAYER_MOCK, loading: 'pending' };
+    }
   });
-  builder.addCase(getLayers.fulfilled, (state, action) => {
-    state.data = action.payload || {
-      layers: [],
-      layersVisibility: {},
-    };
-    state.loading = 'succeeded';
+  builder.addCase(getLayersForModel.fulfilled, (state, action) => {
+    const modelId = action.meta.arg;
+    const data = action.payload || { ...LAYER_STATE_DEFAULT_DATA };
+    if (state[modelId]) {
+      state[modelId].data = data;
+      state[modelId].loading = 'succeeded';
+    } else {
+      state[modelId] = { data, loading: 'pending', error: DEFAULT_ERROR };
+    }
   });
-  builder.addCase(getLayers.rejected, state => {
-    state.loading = 'failed';
+  builder.addCase(getLayersForModel.rejected, (state, action) => {
+    const modelId = action.meta.arg;
+    if (state[modelId]) {
+      state[modelId].loading = 'failed';
+    } else {
+      state[modelId] = { ...LAYERS_STATE_INITIAL_LAYER_MOCK, loading: 'failed' };
+    }
   });
 };
 
 export const setLayerVisibilityReducer = (
   state: LayersState,
-  action: PayloadAction<{ visible: boolean; layerId: string }>,
+  action: PayloadAction<{ modelId: number; visible: boolean; layerId: string }>,
 ): void => {
-  const { payload } = action;
-  if (state.data && state.data.layersVisibility[payload.layerId] !== undefined) {
-    state.data.layersVisibility[payload.layerId] = payload.visible;
+  const { modelId, visible, layerId } = action.payload;
+  const { data } = state[modelId];
+  if (!data) {
+    return;
+  }
+  if (data.layersVisibility[layerId] !== undefined) {
+    data.layersVisibility[layerId] = visible;
   }
 };
diff --git a/src/redux/layers/layers.selectors.ts b/src/redux/layers/layers.selectors.ts
index d29698e0..4d9e3f6d 100644
--- a/src/redux/layers/layers.selectors.ts
+++ b/src/redux/layers/layers.selectors.ts
@@ -1,14 +1,26 @@
 import { createSelector } from '@reduxjs/toolkit';
 import { rootSelector } from '@/redux/root/root.selectors';
+import { currentModelIdSelector } from '@/redux/models/models.selectors';
 
-export const layersSelector = createSelector(
-  rootSelector,
-  state => state.layers?.data?.layers || [],
+export const layersSelector = createSelector(rootSelector, state => state.layers);
+
+export const layersStateForCurrentModelSelector = createSelector(
+  layersSelector,
+  currentModelIdSelector,
+  (state, currentModelId) => state[currentModelId],
+);
+
+export const layersLoadingSelector = createSelector(
+  layersStateForCurrentModelSelector,
+  state => state?.loading,
 );
 
-export const layersLoadingSelector = createSelector(rootSelector, state => state.layers.loading);
+export const layersVisibilityForCurrentModelSelector = createSelector(
+  layersStateForCurrentModelSelector,
+  state => state?.data?.layersVisibility || {},
+);
 
-export const layersVisibilitySelector = createSelector(
-  rootSelector,
-  state => state.layers?.data?.layersVisibility || {},
+export const layersForCurrentModelSelector = createSelector(
+  layersStateForCurrentModelSelector,
+  state => state?.data?.layers || [],
 );
diff --git a/src/redux/layers/layers.slice.ts b/src/redux/layers/layers.slice.ts
index 47da06b0..7c07cdc0 100644
--- a/src/redux/layers/layers.slice.ts
+++ b/src/redux/layers/layers.slice.ts
@@ -1,6 +1,9 @@
 import { createSlice } from '@reduxjs/toolkit';
 import { LAYERS_STATE_INITIAL_MOCK } from '@/redux/layers/layers.mock';
-import { getLayersReducer, setLayerVisibilityReducer } from '@/redux/layers/layers.reducers';
+import {
+  getLayersForModelReducer,
+  setLayerVisibilityReducer,
+} from '@/redux/layers/layers.reducers';
 
 export const layersSlice = createSlice({
   name: 'layers',
@@ -9,7 +12,7 @@ export const layersSlice = createSlice({
     setLayerVisibility: setLayerVisibilityReducer,
   },
   extraReducers: builder => {
-    getLayersReducer(builder);
+    getLayersForModelReducer(builder);
   },
 });
 
diff --git a/src/redux/layers/layers.thunks.test.ts b/src/redux/layers/layers.thunks.test.ts
index a6c00186..bdc626d5 100644
--- a/src/redux/layers/layers.thunks.test.ts
+++ b/src/redux/layers/layers.thunks.test.ts
@@ -7,7 +7,7 @@ import {
 import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
 import { HttpStatusCode } from 'axios';
 import { LayersState } from '@/redux/layers/layers.types';
-import { getLayers } from '@/redux/layers/layers.thunks';
+import { getLayersForModel } from '@/redux/layers/layers.thunks';
 import { layersFixture } from '@/models/fixtures/layersFixture';
 import { layerTextsFixture } from '@/models/fixtures/layerTextsFixture';
 import { layerRectsFixture } from '@/models/fixtures/layerRectsFixture';
@@ -23,7 +23,7 @@ describe('layers thunks', () => {
     store = createStoreInstanceUsingSliceReducer('layers', layersReducer);
   });
 
-  describe('getLayers', () => {
+  describe('getLayersForModel', () => {
     it('should return data when data response from API is valid', async () => {
       mockedAxiosClient.onGet(apiPath.getLayers(1)).reply(HttpStatusCode.Ok, layersFixture);
       mockedAxiosClient
@@ -39,7 +39,7 @@ describe('layers thunks', () => {
         .onGet(apiPath.getLayerLines(1, layersFixture.content[0].id))
         .reply(HttpStatusCode.Ok, layerLinesFixture);
 
-      const { payload } = await store.dispatch(getLayers(1));
+      const { payload } = await store.dispatch(getLayersForModel(1));
       expect(payload).toEqual({
         layers: [
           {
@@ -61,7 +61,7 @@ describe('layers thunks', () => {
         .onGet(apiPath.getLayers(1))
         .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' });
 
-      const { payload } = await store.dispatch(getLayers(1));
+      const { payload } = await store.dispatch(getLayersForModel(1));
       expect(payload).toEqual(undefined);
     });
   });
diff --git a/src/redux/layers/layers.thunks.ts b/src/redux/layers/layers.thunks.ts
index 9aa71e83..0f38e3d1 100644
--- a/src/redux/layers/layers.thunks.ts
+++ b/src/redux/layers/layers.thunks.ts
@@ -15,51 +15,52 @@ import { pageableSchema } from '@/models/pageableSchema';
 import { layerOvalSchema } from '@/models/layerOvalSchema';
 import { layerLineSchema } from '@/models/layerLineSchema';
 
-export const getLayers = createAsyncThunk<LayersVisibilitiesState | undefined, number, ThunkConfig>(
-  'vectorMap/getLayers',
-  async (modelId: number) => {
-    try {
-      const { data } = await axiosInstanceNewAPI.get<Layers>(apiPath.getLayers(modelId));
-      const isDataValid = validateDataUsingZodSchema(data, pageableSchema(layerSchema));
-      if (!isDataValid) {
-        return undefined;
-      }
-      let layers = await Promise.all(
-        data.content.map(async (layer: Layer) => {
-          const [textsResponse, rectsResponse, ovalsResponse, linesResponse] = await Promise.all([
-            axiosInstanceNewAPI.get(apiPath.getLayerTexts(modelId, layer.id)),
-            axiosInstanceNewAPI.get(apiPath.getLayerRects(modelId, layer.id)),
-            axiosInstanceNewAPI.get(apiPath.getLayerOvals(modelId, layer.id)),
-            axiosInstanceNewAPI.get(apiPath.getLayerLines(modelId, layer.id)),
-          ]);
+export const getLayersForModel = createAsyncThunk<
+  LayersVisibilitiesState | undefined,
+  number,
+  ThunkConfig
+>('vectorMap/getLayers', async (modelId: number) => {
+  try {
+    const { data } = await axiosInstanceNewAPI.get<Layers>(apiPath.getLayers(modelId));
+    const isDataValid = validateDataUsingZodSchema(data, pageableSchema(layerSchema));
+    if (!isDataValid) {
+      return undefined;
+    }
+    let layers = await Promise.all(
+      data.content.map(async (layer: Layer) => {
+        const [textsResponse, rectsResponse, ovalsResponse, linesResponse] = await Promise.all([
+          axiosInstanceNewAPI.get(apiPath.getLayerTexts(modelId, layer.id)),
+          axiosInstanceNewAPI.get(apiPath.getLayerRects(modelId, layer.id)),
+          axiosInstanceNewAPI.get(apiPath.getLayerOvals(modelId, layer.id)),
+          axiosInstanceNewAPI.get(apiPath.getLayerLines(modelId, layer.id)),
+        ]);
 
-          return {
-            details: layer,
-            texts: textsResponse.data.content,
-            rects: rectsResponse.data.content,
-            ovals: ovalsResponse.data.content,
-            lines: linesResponse.data.content,
-          };
-        }),
+        return {
+          details: layer,
+          texts: textsResponse.data.content,
+          rects: rectsResponse.data.content,
+          ovals: ovalsResponse.data.content,
+          lines: linesResponse.data.content,
+        };
+      }),
+    );
+    layers = layers.filter(layer => {
+      return (
+        z.array(layerTextSchema).safeParse(layer.texts).success &&
+        z.array(layerRectSchema).safeParse(layer.rects).success &&
+        z.array(layerOvalSchema).safeParse(layer.ovals).success &&
+        z.array(layerLineSchema).safeParse(layer.lines).success
       );
-      layers = layers.filter(layer => {
-        return (
-          z.array(layerTextSchema).safeParse(layer.texts).success &&
-          z.array(layerRectSchema).safeParse(layer.rects).success &&
-          z.array(layerOvalSchema).safeParse(layer.ovals).success &&
-          z.array(layerLineSchema).safeParse(layer.lines).success
-        );
-      });
-      const layersVisibility = layers.reduce((acc: { [key: string]: boolean }, layer) => {
-        acc[layer.details.layerId] = layer.details.visible;
-        return acc;
-      }, {});
-      return {
-        layers,
-        layersVisibility,
-      };
-    } catch (error) {
-      return Promise.reject(getError({ error, prefix: LAYERS_FETCHING_ERROR_PREFIX }));
-    }
-  },
-);
+    });
+    const layersVisibility = layers.reduce((acc: { [key: string]: boolean }, layer) => {
+      acc[layer.details.layerId] = layer.details.visible;
+      return acc;
+    }, {});
+    return {
+      layers,
+      layersVisibility,
+    };
+  } catch (error) {
+    return Promise.reject(getError({ error, prefix: LAYERS_FETCHING_ERROR_PREFIX }));
+  }
+});
diff --git a/src/redux/layers/layers.types.ts b/src/redux/layers/layers.types.ts
index 63637690..c00d6a7e 100644
--- a/src/redux/layers/layers.types.ts
+++ b/src/redux/layers/layers.types.ts
@@ -1,4 +1,4 @@
-import { FetchDataState } from '@/types/fetchDataState';
+import { KeyedFetchDataState } from '@/types/fetchDataState';
 import { Layer, LayerLine, LayerOval, LayerRect, LayerText } from '@/types/models';
 
 export type LayerState = {
@@ -18,4 +18,4 @@ export type LayersVisibilitiesState = {
   layers: LayerState[];
 };
 
-export type LayersState = FetchDataState<LayersVisibilitiesState>;
+export type LayersState = KeyedFetchDataState<LayersVisibilitiesState>;
-- 
GitLab


From 0555d455401e3025dda55ff0c30ea58f23c52f9d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 28 Nov 2024 13:31:26 +0100
Subject: [PATCH 27/29] refactor(vector-map): modify modelElements redux to
 store layers of multiple models

---
 .../reactionsLayer/useOlMapReactionsLayer.ts  | 11 +++++--
 src/redux/modelElements/modelElements.mock.ts |  8 +++--
 .../modelElements.reducers.test.ts            | 24 ++++++--------
 .../modelElements/modelElements.reducers.ts   | 31 +++++++++++++------
 .../modelElements/modelElements.selector.ts   | 18 +++++------
 .../modelElements/modelElements.slice.ts      | 10 ++----
 .../modelElements/modelElements.types.ts      |  6 ++--
 7 files changed, 57 insertions(+), 51 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index 99e05527..1037bd41 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -12,7 +12,10 @@ import {
   lineTypesSelector,
 } from '@/redux/shapes/shapes.selectors';
 import { MapInstance } from '@/types/map';
-import { modelElementsForCurrentModelSelector } from '@/redux/modelElements/modelElements.selector';
+import {
+  modelElementsForCurrentModelSelector,
+  modelElementsLoadingSelector,
+} from '@/redux/modelElements/modelElements.selector';
 import { currentModelIdSelector } from '@/redux/models/models.selectors';
 import { getModelElementsForModel } from '@/redux/modelElements/modelElements.thunks';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
@@ -61,6 +64,8 @@ export const useOlMapReactionsLayer = ({
   const markersRender = parseSurfaceMarkersToBioEntityRender(currentMarkers);
   const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
   const reactionsForCurrentModel = useAppSelector(newReactionsForCurrentModelSelector);
+  const modelElementsLoading = useAppSelector(modelElementsLoadingSelector);
+
   const modelElementsForCurrentModel = useAppSelector(modelElementsForCurrentModelSelector);
   const debouncedBioEntities = useDebouncedValue(bioEntities, 1000);
   const { getOverlayBioEntityColorByAvailableProperties } = useGetOverlayColor();
@@ -79,13 +84,13 @@ export const useOlMapReactionsLayer = ({
     if (!currentModelId) {
       return;
     }
-    if (!modelElementsForCurrentModel) {
+    if (modelElementsLoading !== 'succeeded') {
       dispatch(getModelElementsForModel(currentModelId));
     }
     if (!reactionsForCurrentModel) {
       dispatch(getNewReactionsForModel(currentModelId));
     }
-  }, [currentModelId, dispatch, reactionsForCurrentModel, modelElementsForCurrentModel]);
+  }, [currentModelId, dispatch, reactionsForCurrentModel, modelElementsLoading]);
 
   useEffect(() => {
     if (overlaysOrder.length) {
diff --git a/src/redux/modelElements/modelElements.mock.ts b/src/redux/modelElements/modelElements.mock.ts
index 0ee98c43..583f03f3 100644
--- a/src/redux/modelElements/modelElements.mock.ts
+++ b/src/redux/modelElements/modelElements.mock.ts
@@ -1,8 +1,12 @@
 import { DEFAULT_ERROR } from '@/constants/errors';
 import { ModelElementsState } from '@/redux/modelElements/modelElements.types';
+import { FetchDataState } from '@/types/fetchDataState';
+import { ModelElement } from '@/types/models';
 
-export const MODEL_ELEMENTS_INITIAL_STATE_MOCK: ModelElementsState = {
-  data: {},
+export const MODEL_ELEMENTS_INITIAL_STATE_MOCK: ModelElementsState = {};
+
+export const MODEL_ELEMENTS_STATE_INITIAL_MODEL_MOCK: FetchDataState<Array<ModelElement>> = {
+  data: [],
   loading: 'idle',
   error: DEFAULT_ERROR,
 };
diff --git a/src/redux/modelElements/modelElements.reducers.test.ts b/src/redux/modelElements/modelElements.reducers.test.ts
index 3554f048..fc5c5a6f 100644
--- a/src/redux/modelElements/modelElements.reducers.test.ts
+++ b/src/redux/modelElements/modelElements.reducers.test.ts
@@ -14,12 +14,6 @@ import { modelElementsFixture } from '@/models/fixtures/modelElementsFixture';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
 
-const INITIAL_STATE: ModelElementsState = {
-  data: {},
-  loading: 'idle',
-  error: { name: '', message: '' },
-};
-
 describe('model elements reducer', () => {
   let store = {} as ToolkitStoreWithSingleSlice<ModelElementsState>;
   beforeEach(() => {
@@ -29,7 +23,7 @@ describe('model elements reducer', () => {
   it('should match initial state', () => {
     const action = { type: 'unknown' };
 
-    expect(modelElementsReducer(undefined, action)).toEqual(INITIAL_STATE);
+    expect(modelElementsReducer(undefined, action)).toEqual({});
   });
 
   it('should update store after successful getModelElementsForModel query', async () => {
@@ -38,19 +32,19 @@ describe('model elements reducer', () => {
       .reply(HttpStatusCode.Ok, modelElementsFixture);
 
     const { type } = await store.dispatch(getModelElementsForModel(0));
-    const { data, loading, error } = store.getState().modelElements;
+    const { data, loading, error } = store.getState().modelElements[0];
 
     expect(type).toBe('vectorMap/getModelElementsForModel/fulfilled');
     expect(loading).toEqual('succeeded');
     expect(error).toEqual({ message: '', name: '' });
-    expect(data).toEqual({ 0: modelElementsFixture.content });
+    expect(data).toEqual(modelElementsFixture.content);
   });
 
   it('should update store after failed getModelElementsForModel query', async () => {
     mockedAxiosClient.onGet(apiPath.getModelElementsForModel(0)).reply(HttpStatusCode.NotFound, []);
 
     const action = await store.dispatch(getModelElementsForModel(0));
-    const { data, loading, error } = store.getState().modelElements;
+    const { data, loading, error } = store.getState().modelElements[0];
 
     expect(action.type).toBe('vectorMap/getModelElementsForModel/rejected');
     expect(() => unwrapResult(action)).toThrow(
@@ -58,7 +52,7 @@ describe('model elements reducer', () => {
     );
     expect(loading).toEqual('failed');
     expect(error).toEqual({ message: '', name: '' });
-    expect(data).toEqual({});
+    expect(data).toEqual([]);
   });
 
   it('should update store on loading getModelElementsForModel query', async () => {
@@ -68,15 +62,15 @@ describe('model elements reducer', () => {
 
     const modelElementsPromise = store.dispatch(getModelElementsForModel(0));
 
-    const { data, loading } = store.getState().modelElements;
-    expect(data).toEqual({});
+    const { data, loading } = store.getState().modelElements[0];
+    expect(data).toEqual([]);
     expect(loading).toEqual('pending');
 
     modelElementsPromise.then(() => {
       const { data: dataPromiseFulfilled, loading: promiseFulfilled } =
-        store.getState().modelElements;
+        store.getState().modelElements[0];
 
-      expect(dataPromiseFulfilled).toEqual({ 0: modelElementsFixture.content });
+      expect(dataPromiseFulfilled).toEqual(modelElementsFixture.content);
       expect(promiseFulfilled).toEqual('succeeded');
     });
   });
diff --git a/src/redux/modelElements/modelElements.reducers.ts b/src/redux/modelElements/modelElements.reducers.ts
index 15a40c42..777295cf 100644
--- a/src/redux/modelElements/modelElements.reducers.ts
+++ b/src/redux/modelElements/modelElements.reducers.ts
@@ -1,25 +1,36 @@
 import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
 import { getModelElementsForModel } from '@/redux/modelElements/modelElements.thunks';
 import { ModelElementsState } from '@/redux/modelElements/modelElements.types';
+import { MODEL_ELEMENTS_STATE_INITIAL_MODEL_MOCK } from '@/redux/modelElements/modelElements.mock';
+import { DEFAULT_ERROR } from '@/constants/errors';
 
 export const getModelElementsReducer = (
   builder: ActionReducerMapBuilder<ModelElementsState>,
 ): void => {
-  builder.addCase(getModelElementsForModel.pending, state => {
-    state.loading = 'pending';
+  builder.addCase(getModelElementsForModel.pending, (state, action) => {
+    const modelId = action.meta.arg;
+    if (state[modelId]) {
+      state[modelId].loading = 'pending';
+    } else {
+      state[modelId] = { ...MODEL_ELEMENTS_STATE_INITIAL_MODEL_MOCK, loading: 'pending' };
+    }
   });
   builder.addCase(getModelElementsForModel.fulfilled, (state, action) => {
     const modelId = action.meta.arg;
-    if (state.data) {
-      state.data[modelId] = action.payload || [];
+    const data = action.payload || [];
+    if (state[modelId]) {
+      state[modelId].data = data;
+      state[modelId].loading = 'succeeded';
     } else {
-      state.data = {
-        [modelId]: action.payload || [],
-      };
+      state[modelId] = { data, loading: 'pending', error: DEFAULT_ERROR };
     }
-    state.loading = 'succeeded';
   });
-  builder.addCase(getModelElementsForModel.rejected, state => {
-    state.loading = 'failed';
+  builder.addCase(getModelElementsForModel.rejected, (state, action) => {
+    const modelId = action.meta.arg;
+    if (state[modelId]) {
+      state[modelId].loading = 'failed';
+    } else {
+      state[modelId] = { ...MODEL_ELEMENTS_STATE_INITIAL_MODEL_MOCK, loading: 'failed' };
+    }
   });
 };
diff --git a/src/redux/modelElements/modelElements.selector.ts b/src/redux/modelElements/modelElements.selector.ts
index f9a654bf..4be70ab4 100644
--- a/src/redux/modelElements/modelElements.selector.ts
+++ b/src/redux/modelElements/modelElements.selector.ts
@@ -4,18 +4,18 @@ import { currentModelIdSelector } from '@/redux/models/models.selectors';
 
 export const modelElementsSelector = createSelector(rootSelector, state => state.modelElements);
 
-export const modelElementsLoadingSelector = createSelector(
-  rootSelector,
-  state => state.modelElements.loading,
+export const modelElementsStateForCurrentModelSelector = createSelector(
+  modelElementsSelector,
+  currentModelIdSelector,
+  (state, currentModelId) => state[currentModelId],
 );
 
-export const modelElementsDataSelector = createSelector(
-  modelElementsSelector,
-  modelElements => modelElements.data || {},
+export const modelElementsLoadingSelector = createSelector(
+  modelElementsStateForCurrentModelSelector,
+  state => state?.loading,
 );
 
 export const modelElementsForCurrentModelSelector = createSelector(
-  modelElementsDataSelector,
-  currentModelIdSelector,
-  (data, currentModelId) => data[currentModelId],
+  modelElementsStateForCurrentModelSelector,
+  state => state?.data || [],
 );
diff --git a/src/redux/modelElements/modelElements.slice.ts b/src/redux/modelElements/modelElements.slice.ts
index 56b5d77a..cbf16d00 100644
--- a/src/redux/modelElements/modelElements.slice.ts
+++ b/src/redux/modelElements/modelElements.slice.ts
@@ -1,16 +1,10 @@
 import { createSlice } from '@reduxjs/toolkit';
 import { getModelElementsReducer } from '@/redux/modelElements/modelElements.reducers';
-import { ModelElementsState } from '@/redux/modelElements/modelElements.types';
-
-const initialState: ModelElementsState = {
-  data: {},
-  loading: 'idle',
-  error: { name: '', message: '' },
-};
+import { MODEL_ELEMENTS_INITIAL_STATE_MOCK } from '@/redux/modelElements/modelElements.mock';
 
 export const modelElements = createSlice({
   name: 'modelElements',
-  initialState,
+  initialState: MODEL_ELEMENTS_INITIAL_STATE_MOCK,
   reducers: {},
   extraReducers: builder => {
     getModelElementsReducer(builder);
diff --git a/src/redux/modelElements/modelElements.types.ts b/src/redux/modelElements/modelElements.types.ts
index 808f3ace..b2d4ee68 100644
--- a/src/redux/modelElements/modelElements.types.ts
+++ b/src/redux/modelElements/modelElements.types.ts
@@ -1,6 +1,4 @@
-import { FetchDataState } from '@/types/fetchDataState';
+import { KeyedFetchDataState } from '@/types/fetchDataState';
 import { ModelElement } from '@/types/models';
 
-type ModelElementsRecord = Record<number, Array<ModelElement>>;
-
-export type ModelElementsState = FetchDataState<ModelElementsRecord>;
+export type ModelElementsState = KeyedFetchDataState<Array<ModelElement>>;
-- 
GitLab


From d5dab69bd0e933077d90185cb3602012972b7163 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 28 Nov 2024 13:46:29 +0100
Subject: [PATCH 28/29] refactor(vector-map): modify newReactions redux to
 store layers of multiple models

---
 .../reactionsLayer/useOlMapReactionsLayer.ts  | 10 ++++--
 .../newReactions/newReactions.constants.ts    |  9 ------
 src/redux/newReactions/newReactions.mock.ts   |  8 +++--
 .../newReactions.reducers.test.ts             | 16 +++++-----
 .../newReactions/newReactions.reducers.ts     | 31 +++++++++++++------
 .../newReactions/newReactions.selectors.ts    | 16 +++++-----
 src/redux/newReactions/newReactions.slice.ts  |  4 +--
 src/redux/newReactions/newReactions.types.ts  |  6 ++--
 8 files changed, 54 insertions(+), 46 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index 1037bd41..f2de18c8 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -24,7 +24,10 @@ import CompartmentCircle from '@/components/Map/MapViewer/MapViewerVector/utils/
 import Glyph from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph';
 import CompartmentPathway from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway';
 import Reaction from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction';
-import { newReactionsForCurrentModelSelector } from '@/redux/newReactions/newReactions.selectors';
+import {
+  newReactionsForCurrentModelSelector,
+  newReactionsLoadingSelector,
+} from '@/redux/newReactions/newReactions.selectors';
 import { getNewReactionsForModel } from '@/redux/newReactions/newReactions.thunks';
 import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
 import {
@@ -65,6 +68,7 @@ export const useOlMapReactionsLayer = ({
   const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
   const reactionsForCurrentModel = useAppSelector(newReactionsForCurrentModelSelector);
   const modelElementsLoading = useAppSelector(modelElementsLoadingSelector);
+  const reactionsLoading = useAppSelector(newReactionsLoadingSelector);
 
   const modelElementsForCurrentModel = useAppSelector(modelElementsForCurrentModelSelector);
   const debouncedBioEntities = useDebouncedValue(bioEntities, 1000);
@@ -87,10 +91,10 @@ export const useOlMapReactionsLayer = ({
     if (modelElementsLoading !== 'succeeded') {
       dispatch(getModelElementsForModel(currentModelId));
     }
-    if (!reactionsForCurrentModel) {
+    if (reactionsLoading !== 'succeeded') {
       dispatch(getNewReactionsForModel(currentModelId));
     }
-  }, [currentModelId, dispatch, reactionsForCurrentModel, modelElementsLoading]);
+  }, [currentModelId, dispatch, reactionsLoading, modelElementsLoading]);
 
   useEffect(() => {
     if (overlaysOrder.length) {
diff --git a/src/redux/newReactions/newReactions.constants.ts b/src/redux/newReactions/newReactions.constants.ts
index e30ac5fa..bb4677eb 100644
--- a/src/redux/newReactions/newReactions.constants.ts
+++ b/src/redux/newReactions/newReactions.constants.ts
@@ -1,10 +1 @@
-import { DEFAULT_ERROR } from '@/constants/errors';
-import { NewReactionsState } from '@/redux/newReactions/newReactions.types';
-
-export const NEW_REACTIONS_INITIAL_STATE: NewReactionsState = {
-  data: {},
-  loading: 'idle',
-  error: DEFAULT_ERROR,
-};
-
 export const NEW_REACTIONS_FETCHING_ERROR_PREFIX = 'Failed to fetch new reactions';
diff --git a/src/redux/newReactions/newReactions.mock.ts b/src/redux/newReactions/newReactions.mock.ts
index f9a878ec..5b3c51a8 100644
--- a/src/redux/newReactions/newReactions.mock.ts
+++ b/src/redux/newReactions/newReactions.mock.ts
@@ -1,8 +1,12 @@
 import { DEFAULT_ERROR } from '@/constants/errors';
 import { NewReactionsState } from '@/redux/newReactions/newReactions.types';
+import { FetchDataState } from '@/types/fetchDataState';
+import { NewReaction } from '@/types/models';
 
-export const NEW_REACTIONS_INITIAL_STATE_MOCK: NewReactionsState = {
-  data: {},
+export const NEW_REACTIONS_INITIAL_STATE_MOCK: NewReactionsState = {};
+
+export const NEW_REACTIONS_STATE_INITIAL_REACTIONS_MOCK: FetchDataState<Array<NewReaction>> = {
+  data: [],
   loading: 'idle',
   error: DEFAULT_ERROR,
 };
diff --git a/src/redux/newReactions/newReactions.reducers.test.ts b/src/redux/newReactions/newReactions.reducers.test.ts
index 206d34a7..0ceaa5b4 100644
--- a/src/redux/newReactions/newReactions.reducers.test.ts
+++ b/src/redux/newReactions/newReactions.reducers.test.ts
@@ -35,18 +35,18 @@ describe('newReactions reducer', () => {
       .reply(HttpStatusCode.Ok, newReactionsFixture);
 
     const { type } = await store.dispatch(getNewReactionsForModel(1));
-    const { data, loading, error } = store.getState().newReactions;
+    const { data, loading, error } = store.getState().newReactions[1];
     expect(type).toBe('newReactions/getNewReactionsForModel/fulfilled');
     expect(loading).toEqual('succeeded');
     expect(error).toEqual({ message: '', name: '' });
-    expect(data).toEqual({ 1: newReactionsFixture.content });
+    expect(data).toEqual(newReactionsFixture.content);
   });
 
   it('should update store after failed getNewReactionsForModel query', async () => {
     mockedAxiosClient.onGet(apiPath.getNewReactionsForModel(1)).reply(HttpStatusCode.NotFound, []);
 
     const action = await store.dispatch(getNewReactionsForModel(1));
-    const { data, loading, error } = store.getState().newReactions;
+    const { data, loading, error } = store.getState().newReactions[1];
 
     expect(action.type).toBe('newReactions/getNewReactionsForModel/rejected');
     expect(() => unwrapResult(action)).toThrow(
@@ -54,7 +54,7 @@ describe('newReactions reducer', () => {
     );
     expect(loading).toEqual('failed');
     expect(error).toEqual({ message: '', name: '' });
-    expect(data).toEqual({});
+    expect(data).toEqual([]);
   });
 
   it('should update store on loading getNewReactionsForModel query', async () => {
@@ -64,15 +64,15 @@ describe('newReactions reducer', () => {
 
     const newReactionsPromise = store.dispatch(getNewReactionsForModel(1));
 
-    const { data, loading } = store.getState().newReactions;
-    expect(data).toEqual({});
+    const { data, loading } = store.getState().newReactions[1];
+    expect(data).toEqual([]);
     expect(loading).toEqual('pending');
 
     newReactionsPromise.then(() => {
       const { data: dataPromiseFulfilled, loading: promiseFulfilled } =
-        store.getState().newReactions;
+        store.getState().newReactions[1];
 
-      expect(dataPromiseFulfilled).toEqual({ 1: newReactionsFixture.content });
+      expect(dataPromiseFulfilled).toEqual(newReactionsFixture.content);
       expect(promiseFulfilled).toEqual('succeeded');
     });
   });
diff --git a/src/redux/newReactions/newReactions.reducers.ts b/src/redux/newReactions/newReactions.reducers.ts
index 70454483..c8963df9 100644
--- a/src/redux/newReactions/newReactions.reducers.ts
+++ b/src/redux/newReactions/newReactions.reducers.ts
@@ -1,25 +1,36 @@
 import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
 import { getNewReactionsForModel } from '@/redux/newReactions/newReactions.thunks';
 import { NewReactionsState } from '@/redux/newReactions/newReactions.types';
+import { NEW_REACTIONS_STATE_INITIAL_REACTIONS_MOCK } from '@/redux/newReactions/newReactions.mock';
+import { DEFAULT_ERROR } from '@/constants/errors';
 
 export const getNewReactionsReducer = (
   builder: ActionReducerMapBuilder<NewReactionsState>,
 ): void => {
-  builder.addCase(getNewReactionsForModel.pending, state => {
-    state.loading = 'pending';
+  builder.addCase(getNewReactionsForModel.pending, (state, action) => {
+    const modelId = action.meta.arg;
+    if (state[modelId]) {
+      state[modelId].loading = 'pending';
+    } else {
+      state[modelId] = { ...NEW_REACTIONS_STATE_INITIAL_REACTIONS_MOCK, loading: 'pending' };
+    }
   });
   builder.addCase(getNewReactionsForModel.fulfilled, (state, action) => {
     const modelId = action.meta.arg;
-    if (state.data) {
-      state.data[modelId] = action.payload || [];
+    const data = action.payload || [];
+    if (state[modelId]) {
+      state[modelId].data = data;
+      state[modelId].loading = 'succeeded';
     } else {
-      state.data = {
-        [modelId]: action.payload || [],
-      };
+      state[modelId] = { data, loading: 'pending', error: DEFAULT_ERROR };
     }
-    state.loading = 'succeeded';
   });
-  builder.addCase(getNewReactionsForModel.rejected, state => {
-    state.loading = 'failed';
+  builder.addCase(getNewReactionsForModel.rejected, (state, action) => {
+    const modelId = action.meta.arg;
+    if (state[modelId]) {
+      state[modelId].loading = 'failed';
+    } else {
+      state[modelId] = { ...NEW_REACTIONS_STATE_INITIAL_REACTIONS_MOCK, loading: 'failed' };
+    }
   });
 };
diff --git a/src/redux/newReactions/newReactions.selectors.ts b/src/redux/newReactions/newReactions.selectors.ts
index eb461818..6bf11f0e 100644
--- a/src/redux/newReactions/newReactions.selectors.ts
+++ b/src/redux/newReactions/newReactions.selectors.ts
@@ -4,18 +4,18 @@ import { rootSelector } from '../root/root.selectors';
 
 export const newReactionsSelector = createSelector(rootSelector, state => state.newReactions);
 
-export const newReactionsLoadingSelector = createSelector(
+export const newReactionsStateForCurrentModelSelector = createSelector(
   newReactionsSelector,
-  state => state.loading,
+  currentModelIdSelector,
+  (state, currentModelId) => state[currentModelId],
 );
 
-export const newReactionsDataSelector = createSelector(
-  newReactionsSelector,
-  reactions => reactions.data || {},
+export const newReactionsLoadingSelector = createSelector(
+  newReactionsStateForCurrentModelSelector,
+  state => state?.loading,
 );
 
 export const newReactionsForCurrentModelSelector = createSelector(
-  newReactionsDataSelector,
-  currentModelIdSelector,
-  (data, currentModelId) => data[currentModelId],
+  newReactionsStateForCurrentModelSelector,
+  state => state?.data || [],
 );
diff --git a/src/redux/newReactions/newReactions.slice.ts b/src/redux/newReactions/newReactions.slice.ts
index 5bb5198a..d603e579 100644
--- a/src/redux/newReactions/newReactions.slice.ts
+++ b/src/redux/newReactions/newReactions.slice.ts
@@ -1,10 +1,10 @@
 import { createSlice } from '@reduxjs/toolkit';
-import { NEW_REACTIONS_INITIAL_STATE } from '@/redux/newReactions/newReactions.constants';
 import { getNewReactionsReducer } from '@/redux/newReactions/newReactions.reducers';
+import { NEW_REACTIONS_INITIAL_STATE_MOCK } from '@/redux/newReactions/newReactions.mock';
 
 export const newReactionsSlice = createSlice({
   name: 'reactions',
-  initialState: NEW_REACTIONS_INITIAL_STATE,
+  initialState: NEW_REACTIONS_INITIAL_STATE_MOCK,
   reducers: {},
   extraReducers: builder => {
     getNewReactionsReducer(builder);
diff --git a/src/redux/newReactions/newReactions.types.ts b/src/redux/newReactions/newReactions.types.ts
index 7b36d1c5..aff62402 100644
--- a/src/redux/newReactions/newReactions.types.ts
+++ b/src/redux/newReactions/newReactions.types.ts
@@ -1,6 +1,4 @@
-import { FetchDataState } from '@/types/fetchDataState';
+import { KeyedFetchDataState } from '@/types/fetchDataState';
 import { NewReaction } from '@/types/models';
 
-type NewReactionsRecord = Record<number, NewReaction[]>;
-
-export type NewReactionsState = FetchDataState<NewReactionsRecord>;
+export type NewReactionsState = KeyedFetchDataState<Array<NewReaction>>;
-- 
GitLab


From d4e93e3769ccd0a0757669137c774a5fc0bc1420 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl>
Date: Thu, 28 Nov 2024 13:52:14 +0100
Subject: [PATCH 29/29] fix(vector-map): change condition of when to featch map
 data

---
 .../utils/config/additionalLayers/useOlMapAdditionalLayers.ts | 2 +-
 .../utils/config/reactionsLayer/useOlMapReactionsLayer.ts     | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
index f56532ef..b37cffda 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts
@@ -42,7 +42,7 @@ export const useOlMapAdditionalLayers = (
     if (!currentModelId) {
       return;
     }
-    if (layersLoading !== 'succeeded') {
+    if (!['succeeded', 'pending'].includes(layersLoading)) {
       dispatch(getLayersForModel(currentModelId));
     }
   }, [currentModelId, dispatch, layersLoading]);
diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
index f2de18c8..6f2aafa1 100644
--- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
+++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts
@@ -88,10 +88,10 @@ export const useOlMapReactionsLayer = ({
     if (!currentModelId) {
       return;
     }
-    if (modelElementsLoading !== 'succeeded') {
+    if (!['succeeded', 'pending'].includes(modelElementsLoading)) {
       dispatch(getModelElementsForModel(currentModelId));
     }
-    if (reactionsLoading !== 'succeeded') {
+    if (!['succeeded', 'pending'].includes(reactionsLoading)) {
       dispatch(getNewReactionsForModel(currentModelId));
     }
   }, [currentModelId, dispatch, reactionsLoading, modelElementsLoading]);
-- 
GitLab