diff --git a/setupTests.ts b/setupTests.ts
index 1d944c81f3e4f47f19d599c4f702725886518791..d4cfed94b5591e04245725226e8327b4ec03b954 100644
--- a/setupTests.ts
+++ b/setupTests.ts
@@ -33,6 +33,8 @@ const localStorageMock = (() => {
   };
 })();
 
+global.structuredClone = val => JSON.parse(JSON.stringify(val));
+
 Object.defineProperty(global, 'localStorage', {
   value: localStorageMock,
 });
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createFeatureFromExtent.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createFeatureFromExtent.ts
new file mode 100644
index 0000000000000000000000000000000000000000..121bfe2bb2b3c25f79ecb2f0dd7809a4378ace40
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createFeatureFromExtent.ts
@@ -0,0 +1,5 @@
+import Polygon, { fromExtent } from 'ol/geom/Polygon';
+import Feature from 'ol/Feature';
+
+export const createFeatureFromExtent = ([xMin, yMin, xMax, yMax]: number[]): Feature<Polygon> =>
+  new Feature({ geometry: fromExtent([xMin, yMin, xMax, yMax]) });
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts
index 9f27f8567185a59c2a6bffb5a94a56ca857d8ec4..b294d492153d7f74aea80a7836f30c0557032ba5 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts
@@ -1,10 +1,7 @@
 import { Fill, Stroke, Style } from 'ol/style';
-import { fromExtent } from 'ol/geom/Polygon';
 import Feature from 'ol/Feature';
 import type Polygon from 'ol/geom/Polygon';
-
-const createFeatureFromExtent = ([xMin, yMin, xMax, yMax]: number[]): Feature<Polygon> =>
-  new Feature({ geometry: fromExtent([xMin, yMin, xMax, yMax]) });
+import { createFeatureFromExtent } from './createFeatureFromExtent';
 
 const getBioEntityOverlayFeatureStyle = (color: string): Style =>
   new Style({ fill: new Fill({ color }), stroke: new Stroke({ color: 'black', width: 1 }) });
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..06d6074af86160a0318c919e1cda4a95105be466
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.test.ts
@@ -0,0 +1,67 @@
+/* eslint-disable no-magic-numbers */
+import Feature from 'ol/Feature';
+import { createOverlaySubmapLinkRectangleFeature } from './createOverlaySubmapLinkRectangleFeature';
+
+const COLOR = '#FFFFFFcc';
+
+const CASES = [
+  [
+    [0, 0, 0, 0],
+    [0, 0, 0, 0],
+  ],
+  [
+    [0, 0, 100, 100],
+    [0, 0, 100, 100],
+  ],
+  [
+    [100, 0, 230, 100],
+    [100, 0, 230, 100],
+  ],
+  [
+    [-50, 0, 0, 50],
+    [-50, 0, 0, 50],
+  ],
+];
+
+describe('createOverlaySubmapLinkRectangleFeature - util', () => {
+  it.each(CASES)('should return Feature instance', points => {
+    const feature = createOverlaySubmapLinkRectangleFeature(points, COLOR);
+
+    expect(feature).toBeInstanceOf(Feature);
+  });
+
+  it.each(CASES)('should return Feature instance with valid style and stroke', points => {
+    const feature = createOverlaySubmapLinkRectangleFeature(points, COLOR);
+    const style = feature.getStyle();
+
+    expect(style).toMatchObject({
+      fill_: { color_: COLOR },
+      stroke_: {
+        color_: COLOR,
+        width_: 1,
+      },
+    });
+  });
+  it('should return object with transparent fill and black stroke color when color is null', () => {
+    const feature = createOverlaySubmapLinkRectangleFeature([0, 0, 0, 0], null);
+    const style = feature.getStyle();
+
+    expect(style).toMatchObject({
+      fill_: { color_: 'transparent' },
+      stroke_: {
+        color_: 'black',
+        width_: 1,
+      },
+    });
+  });
+  it.each(CASES)('should return Feature instance with valid geometry', (points, extent) => {
+    const feature = createOverlaySubmapLinkRectangleFeature(points, COLOR);
+    const geometry = feature.getGeometry();
+
+    expect(geometry?.getExtent()).toEqual(extent);
+  });
+
+  it('should throw error if extent is not valid', () => {
+    expect(() => createOverlaySubmapLinkRectangleFeature([100, 100, 0, 0], COLOR)).toThrow();
+  });
+});
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cef983542a7777c57a8c551ed6e9d96aa1dcb35c
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlaySubmapLinkRectangleFeature.ts
@@ -0,0 +1,14 @@
+/* eslint-disable no-magic-numbers */
+import Feature from 'ol/Feature';
+import type Polygon from 'ol/geom/Polygon';
+import { createFeatureFromExtent } from './createFeatureFromExtent';
+import { getOverlaySubmapLinkRectangleFeatureStyle } from './getOverlaySubmapLinkRectangleFeatureStyle';
+
+export const createOverlaySubmapLinkRectangleFeature = (
+  [xMin, yMin, xMax, yMax]: number[],
+  color: string | null,
+): Feature<Polygon> => {
+  const feature = createFeatureFromExtent([xMin, yMin, xMax, yMax]);
+  feature.setStyle(getOverlaySubmapLinkRectangleFeatureStyle(color));
+  return feature;
+};
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlaySubmapLinkRectangleFeatureStyle.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlaySubmapLinkRectangleFeatureStyle.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..90bb4af947e8dc4e7978b62ae4135bec61841c3f
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlaySubmapLinkRectangleFeatureStyle.test.ts
@@ -0,0 +1,37 @@
+/* eslint-disable no-magic-numbers */
+import { Fill, Stroke, Style } from 'ol/style';
+import { getOverlaySubmapLinkRectangleFeatureStyle } from './getOverlaySubmapLinkRectangleFeatureStyle';
+
+const COLORS = ['#000000', '#FFFFFF', '#F5F5F5', '#C0C0C0', '#C0C0C0aa', '#C0C0C0bb'];
+
+describe('getOverlaySubmapLinkRectangleFeatureStyle - util', () => {
+  it.each(COLORS)('should return Style object', color => {
+    const result = getOverlaySubmapLinkRectangleFeatureStyle(color);
+    expect(result).toBeInstanceOf(Style);
+  });
+
+  it.each(COLORS)('should set valid color values for fill', color => {
+    const result = getOverlaySubmapLinkRectangleFeatureStyle(color);
+    const fill = result.getFill();
+    expect(fill).toBeInstanceOf(Fill);
+    expect(fill?.getColor()).toBe(color);
+  });
+
+  it.each(COLORS)('should set valid color values for fill', color => {
+    const result = getOverlaySubmapLinkRectangleFeatureStyle(color);
+    const stroke = result.getStroke();
+    expect(stroke).toBeInstanceOf(Stroke);
+    expect(stroke?.getColor()).toBe(color);
+    expect(stroke?.getWidth()).toBe(1);
+  });
+  it('should set transparent fill and black stroke if color is null', () => {
+    const result = getOverlaySubmapLinkRectangleFeatureStyle(null);
+    const stroke = result.getStroke();
+    expect(stroke).toBeInstanceOf(Stroke);
+    expect(stroke?.getColor()).toBe('black');
+    expect(stroke?.getWidth()).toBe(1);
+    const fill = result.getFill();
+    expect(fill).toBeInstanceOf(Fill);
+    expect(fill?.getColor()).toBe('transparent');
+  });
+});
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlaySubmapLinkRectangleFeatureStyle.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlaySubmapLinkRectangleFeatureStyle.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b597c42d4849e21c85b1e0a84e0ede947961df1b
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlaySubmapLinkRectangleFeatureStyle.ts
@@ -0,0 +1,9 @@
+/* eslint-disable no-magic-numbers */
+import { Fill, Stroke, Style } from 'ol/style';
+
+export const getOverlaySubmapLinkRectangleFeatureStyle = (color: string | null): Style =>
+  new Style({
+    fill: new Fill({ color: color || 'transparent' }),
+    stroke: new Stroke({ color: color || 'black', width: 1 }),
+    zIndex: color ? 0 : 1,
+  });
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/getSubmapLinkRectangle.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/getSubmapLinkRectangle.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ddeea9e4187c652b3f7a397be18fa678956123a0
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/getSubmapLinkRectangle.ts
@@ -0,0 +1,26 @@
+/* eslint-disable no-magic-numbers, no-param-reassign */
+import type { SubmapLinkRectangle } from './useBioEntitiesWithSubmapLinks';
+
+export const getSubmapLinkRectangle = (
+  submapsLinksRectangles: SubmapLinkRectangle[],
+  submapLinkRectangle: SubmapLinkRectangle,
+  index: number,
+  submapLinksRectanglesGroup: SubmapLinkRectangle[],
+  rectangleHeight: number,
+): void => {
+  if (index === 0) {
+    submapsLinksRectangles.push({
+      ...structuredClone(submapLinkRectangle),
+      amount: 0,
+      value: Infinity,
+    });
+  }
+
+  if (index !== 0) {
+    submapLinkRectangle.y2 = submapLinksRectanglesGroup[index - 1].y1;
+  }
+  submapLinkRectangle.y1 = submapLinkRectangle.y2 + rectangleHeight;
+  submapLinkRectangle.height = rectangleHeight;
+
+  submapsLinksRectangles.push(submapLinkRectangle);
+};
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/groupSubmapLinksRectanglesById.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/groupSubmapLinksRectanglesById.ts
new file mode 100644
index 0000000000000000000000000000000000000000..67e2476a8e0ec8c0df2264e458ddb5fb169b8f3c
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/groupSubmapLinksRectanglesById.ts
@@ -0,0 +1,45 @@
+import type { OverlayBioEntityRender } from '@/types/OLrendering';
+import type { GroupedSubmapsLinksRectangles } from './useBioEntitiesWithSubmapLinks';
+
+export const groupSubmapLinksRectanglesById = (
+  data: OverlayBioEntityRender[],
+): GroupedSubmapsLinksRectangles => {
+  const submapsLinksRectangles = structuredClone(data);
+  const groupedSubmapsLinksRectanglesById: GroupedSubmapsLinksRectangles = {};
+
+  submapsLinksRectangles.forEach(submapLinkRectangle => {
+    const { id, overlayId } = submapLinkRectangle;
+    const groupId = `${id}-${overlayId}`;
+
+    if (!groupedSubmapsLinksRectanglesById[groupId]) {
+      groupedSubmapsLinksRectanglesById[groupId] = [];
+    }
+
+    const matchedSubmapLinkRectangle = groupedSubmapsLinksRectanglesById[groupId].find(element => {
+      const hasAllRequiredValueProperties = element.value && submapLinkRectangle.value;
+      const isValueEqual =
+        hasAllRequiredValueProperties && element.value === submapLinkRectangle.value;
+
+      const hasAllRequiredColorProperties = element.color && submapLinkRectangle.color;
+      const isColorEqual =
+        hasAllRequiredColorProperties &&
+        element.color?.alpha === submapLinkRectangle?.color?.alpha &&
+        element.color?.rgb === submapLinkRectangle?.color?.rgb;
+
+      if (isValueEqual || isColorEqual) return true;
+
+      return false;
+    });
+
+    if (!matchedSubmapLinkRectangle) {
+      groupedSubmapsLinksRectanglesById[groupId].push({
+        ...structuredClone(submapLinkRectangle),
+        amount: 1,
+      });
+    } else {
+      matchedSubmapLinkRectangle.amount += 1;
+    }
+  });
+
+  return groupedSubmapsLinksRectanglesById;
+};
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useBioEntitiesWithSubmapLinks.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useBioEntitiesWithSubmapLinks.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..788b89f248c1afb9a64d4d39b420c18b8a39c40a
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useBioEntitiesWithSubmapLinks.test.ts
@@ -0,0 +1,331 @@
+/* eslint-disable no-magic-numbers */
+import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
+import { renderHook } from '@testing-library/react';
+import { CONFIGURATION_INITIAL_STORE_MOCKS } from '@/redux/configuration/configuration.mock';
+import { PUBLIC_OVERLAYS_MOCK } from '@/redux/overlays/overlays.mock';
+import { mapStateWithCurrentlySelectedMainMapFixture } from '@/redux/map/map.fixtures';
+import {
+  MOCKED_OVERLAY_SUBMAPS_LINKS_WITH_DIFFERENT_COLORS,
+  MOCKED_OVERLAY_SUBMAPS_LINKS_WITH_SAME_COLORS,
+  OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK,
+} from '@/redux/overlayBioEntity/overlayBioEntity.mock';
+import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock';
+import { useBioEntitiesWithSubmapsLinks } from './useBioEntitiesWithSubmapLinks';
+
+const RESULT_SUBMAP_LINKS_DIFFERENT_VALUES = [
+  {
+    type: 'submap-link',
+    id: 97,
+    modelId: 52,
+    amount: 0,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 100,
+    value: Infinity,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    amount: 1,
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2025.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 25,
+    value: 0.8,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    amount: 1,
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2050.5,
+    y2: 2025.5,
+    overlayId: 12,
+    height: 25,
+    value: 0.5,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    amount: 1,
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2075.5,
+    y2: 2050.5,
+    overlayId: 12,
+    height: 25,
+    value: 0.4,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    amount: 1,
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2075.5,
+    overlayId: 12,
+    height: 25,
+    value: null,
+    color: {
+      alpha: 255,
+      rgb: -2348283,
+    },
+  },
+  {
+    type: 'rectangle',
+    id: 1,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 3128.653195488725,
+    y2: 3088.653195488725,
+    overlayId: 12,
+    height: 10,
+    value: 0,
+    color: null,
+  },
+];
+
+export const RESULT_SUBMAP_LINKS_SAME_COLORS = [
+  {
+    type: 'submap-link',
+    amount: 0,
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 100,
+    value: Infinity,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    amount: 2,
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2050.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 50,
+    value: 23,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    amount: 2,
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2050.5,
+    overlayId: 12,
+    height: 50,
+    value: null,
+    color: {
+      alpha: 255,
+      rgb: -2348283,
+    },
+  },
+  {
+    type: 'rectangle',
+    id: 1,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 3128.653195488725,
+    y2: 3088.653195488725,
+    overlayId: 12,
+    height: 10,
+    value: 0,
+    color: null,
+  },
+];
+
+describe('useBioEntitiesWithSubmapsLinks', () => {
+  it('should return bioEntities without submaps links if no submaps links are present', () => {
+    const { Wrapper } = getReduxStoreWithActionsListener({
+      overlayBioEntity: {
+        ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK,
+        overlaysId: PUBLIC_OVERLAYS_MOCK.map(o => o.idObject),
+      },
+      configuration: CONFIGURATION_INITIAL_STORE_MOCKS,
+      map: mapStateWithCurrentlySelectedMainMapFixture,
+    });
+
+    const {
+      result: { current },
+    } = renderHook(() => useBioEntitiesWithSubmapsLinks(), {
+      wrapper: Wrapper,
+    });
+
+    expect(current).toEqual([]);
+  });
+
+  describe('submap links with the same ID and overlayID but different values or colors', () => {
+    it('should create submap link with Infinity value, for displaying black border of submap link', () => {
+      const { Wrapper } = getReduxStoreWithActionsListener({
+        overlayBioEntity: {
+          overlaysId: [12],
+          data: {
+            12: {
+              52: MOCKED_OVERLAY_SUBMAPS_LINKS_WITH_DIFFERENT_COLORS,
+            },
+          },
+        },
+        configuration: CONFIGURATION_INITIAL_STORE_MOCKS,
+        map: mapStateWithCurrentlySelectedMainMapFixture,
+
+        models: {
+          ...MODELS_DATA_MOCK_WITH_MAIN_MAP,
+        },
+      });
+
+      const {
+        result: { current },
+      } = renderHook(() => useBioEntitiesWithSubmapsLinks(), {
+        wrapper: Wrapper,
+      });
+
+      expect(current[0]).toEqual({
+        type: 'submap-link',
+        amount: 0,
+        id: 97,
+        modelId: 52,
+        width: 30,
+        x1: 18412,
+        x2: 18492,
+        y1: 2100.5,
+        y2: 2000.5,
+        overlayId: 12,
+        height: 100,
+        value: Infinity,
+        color: null,
+      });
+    });
+    it('should modify height, coordinates and return in sorted order to create submap link from several submap link rectangles', () => {
+      const { Wrapper } = getReduxStoreWithActionsListener({
+        overlayBioEntity: {
+          overlaysId: [12],
+          data: {
+            12: {
+              52: MOCKED_OVERLAY_SUBMAPS_LINKS_WITH_DIFFERENT_COLORS,
+            },
+          },
+        },
+        configuration: CONFIGURATION_INITIAL_STORE_MOCKS,
+        map: mapStateWithCurrentlySelectedMainMapFixture,
+
+        models: {
+          ...MODELS_DATA_MOCK_WITH_MAIN_MAP,
+        },
+      });
+
+      const {
+        result: { current },
+      } = renderHook(() => useBioEntitiesWithSubmapsLinks(), {
+        wrapper: Wrapper,
+      });
+
+      expect(current).toStrictEqual(RESULT_SUBMAP_LINKS_DIFFERENT_VALUES);
+    });
+  });
+  describe('submap links with the same ID and overlayID and the same values or colors', () => {
+    it('should create submap link with Infinity value, for displaying black border of submap link', () => {
+      const { Wrapper } = getReduxStoreWithActionsListener({
+        overlayBioEntity: {
+          overlaysId: [12],
+          data: {
+            12: {
+              52: MOCKED_OVERLAY_SUBMAPS_LINKS_WITH_SAME_COLORS,
+            },
+          },
+        },
+        configuration: CONFIGURATION_INITIAL_STORE_MOCKS,
+        map: mapStateWithCurrentlySelectedMainMapFixture,
+
+        models: {
+          ...MODELS_DATA_MOCK_WITH_MAIN_MAP,
+        },
+      });
+
+      const {
+        result: { current },
+      } = renderHook(() => useBioEntitiesWithSubmapsLinks(), {
+        wrapper: Wrapper,
+      });
+
+      expect(current[0]).toEqual({
+        type: 'submap-link',
+        amount: 0,
+        id: 97,
+        modelId: 52,
+        width: 30,
+        x1: 18412,
+        x2: 18492,
+        y1: 2100.5,
+        y2: 2000.5,
+        overlayId: 12,
+        height: 100,
+        value: Infinity,
+        color: null,
+      });
+    });
+    it('should modify height, coordinates and return in sorted order to create submap link from several submap link rectangles', () => {
+      const { Wrapper } = getReduxStoreWithActionsListener({
+        overlayBioEntity: {
+          overlaysId: [12],
+          data: {
+            12: {
+              52: MOCKED_OVERLAY_SUBMAPS_LINKS_WITH_SAME_COLORS,
+            },
+          },
+        },
+        configuration: CONFIGURATION_INITIAL_STORE_MOCKS,
+        map: mapStateWithCurrentlySelectedMainMapFixture,
+
+        models: {
+          ...MODELS_DATA_MOCK_WITH_MAIN_MAP,
+        },
+      });
+
+      const {
+        result: { current },
+      } = renderHook(() => useBioEntitiesWithSubmapsLinks(), {
+        wrapper: Wrapper,
+      });
+
+      expect(current).toStrictEqual(RESULT_SUBMAP_LINKS_SAME_COLORS);
+    });
+  });
+});
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useBioEntitiesWithSubmapLinks.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useBioEntitiesWithSubmapLinks.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fd9ef50e007133e8339f9952c3b3bfd54b491a39
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useBioEntitiesWithSubmapLinks.ts
@@ -0,0 +1,92 @@
+/* eslint-disable no-magic-numbers */
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { overlayBioEntitiesForCurrentModelSelector } from '@/redux/overlayBioEntity/overlayBioEntity.selector';
+import { useCallback, useMemo } from 'react';
+import type { OverlayBioEntityRender } from '@/types/OLrendering';
+import { useGetOverlayColor } from './useGetOverlayColor';
+import { getSubmapLinkRectangle } from './getSubmapLinkRectangle';
+import { groupSubmapLinksRectanglesById } from './groupSubmapLinksRectanglesById';
+
+export type SubmapLinkRectangle = OverlayBioEntityRender & {
+  amount: number;
+};
+
+export type GroupedSubmapsLinksRectangles = {
+  [id: string]: SubmapLinkRectangle[];
+};
+
+export const useBioEntitiesWithSubmapsLinks = (): OverlayBioEntityRender[] => {
+  const { getOverlayBioEntityColorByAvailableProperties } = useGetOverlayColor();
+  const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
+  const submapsLinks = useMemo(
+    () => bioEntities.filter(bioEntity => bioEntity.type === 'submap-link'),
+    [bioEntities],
+  );
+  const bioEntitiesWithoutSubmapsLinks = useMemo(
+    () => bioEntities.filter(bioEntity => bioEntity.type !== 'submap-link'),
+    [bioEntities],
+  );
+
+  const sortSubmapLinksRectanglesByColor = useCallback(
+    (submapLinksRectangles: SubmapLinkRectangle[]): void => {
+      submapLinksRectangles.sort((a, b) => {
+        const firstSubmapLinkRectangleColor = getOverlayBioEntityColorByAvailableProperties(a);
+        const secondSubmapLinkRectangleColor = getOverlayBioEntityColorByAvailableProperties(b);
+
+        if (firstSubmapLinkRectangleColor === secondSubmapLinkRectangleColor) {
+          return 0;
+        }
+
+        return firstSubmapLinkRectangleColor < secondSubmapLinkRectangleColor ? -1 : 1;
+      });
+    },
+    [getOverlayBioEntityColorByAvailableProperties],
+  );
+
+  const calculateSubmapsLinksRectanglesPosition = useCallback(
+    (
+      groupedSubmapsLinksRectanglesById: GroupedSubmapsLinksRectangles,
+    ): OverlayBioEntityRender[] => {
+      const submapsLinksRectangles: SubmapLinkRectangle[] = [];
+      // eslint-disable-next-line no-restricted-syntax, guard-for-in
+      for (const id in groupedSubmapsLinksRectanglesById) {
+        const submapLinksRectanglesGroup = groupedSubmapsLinksRectanglesById[id];
+
+        sortSubmapLinksRectanglesByColor(submapLinksRectanglesGroup);
+
+        const submapLinkRectanglesTotalHeight = submapLinksRectanglesGroup[0].height;
+        const submapLinkRectanglesAmount = submapLinksRectanglesGroup.reduce(
+          (accumulator: number, currentValue) => accumulator + currentValue.amount,
+          0,
+        );
+
+        submapLinksRectanglesGroup.forEach((submapLinkRectangle, index) => {
+          const ratio = submapLinkRectangle.amount / submapLinkRectanglesAmount;
+          const rectangleHeight = ratio * submapLinkRectanglesTotalHeight;
+
+          getSubmapLinkRectangle(
+            submapsLinksRectangles,
+            submapLinkRectangle,
+            index,
+            submapLinksRectanglesGroup,
+            rectangleHeight,
+          );
+        });
+      }
+
+      return submapsLinksRectangles;
+    },
+    [sortSubmapLinksRectanglesByColor],
+  );
+
+  const groupedSubmapLinksRectanglesById = useMemo(
+    () => groupSubmapLinksRectanglesById(submapsLinks),
+    [submapsLinks],
+  );
+  const submapsLinksRectangles = useMemo(
+    () => calculateSubmapsLinksRectanglesPosition(groupedSubmapLinksRectanglesById),
+    [groupedSubmapLinksRectanglesById, calculateSubmapsLinksRectanglesPosition],
+  );
+
+  return [...submapsLinksRectangles, ...bioEntitiesWithoutSubmapsLinks];
+};
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts
index 477a95dd324f06f8099d88db81055e7324b77d5f..9bb3971163d60169f79d09516d922e3ab68bd85e 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts
@@ -1,9 +1,7 @@
+/* eslint-disable no-magic-numbers */
 import { ZERO } from '@/constants/common';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
-import {
-  getOverlayOrderSelector,
-  overlayBioEntitiesForCurrentModelSelector,
-} from '@/redux/overlayBioEntity/overlayBioEntity.selector';
+import { getOverlayOrderSelector } from '@/redux/overlayBioEntity/overlayBioEntity.selector';
 import { LinePoint } from '@/types/reactions';
 import { usePointToProjection } from '@/utils/map/usePointToProjection';
 import type Feature from 'ol/Feature';
@@ -14,12 +12,14 @@ import { createOverlayGeometryFeature } from './createOverlayGeometryFeature';
 import { createOverlayLineFeature } from './createOverlayLineFeature';
 import { getPolygonLatitudeCoordinates } from './getPolygonLatitudeCoordinates';
 import { useGetOverlayColor } from './useGetOverlayColor';
+import { createOverlaySubmapLinkRectangleFeature } from './createOverlaySubmapLinkRectangleFeature';
+import { useBioEntitiesWithSubmapsLinks } from './useBioEntitiesWithSubmapLinks';
 
 export const useOverlayFeatures = (): Feature<Polygon>[] | Feature<SimpleGeometry>[] => {
   const pointToProjection = usePointToProjection();
   const { getOverlayBioEntityColorByAvailableProperties } = useGetOverlayColor();
   const overlaysOrder = useAppSelector(getOverlayOrderSelector);
-  const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
+  const bioEntities = useBioEntitiesWithSubmapsLinks();
 
   const features = useMemo(
     () =>
@@ -39,6 +39,16 @@ export const useOverlayFeatures = (): Feature<Polygon>[] | Feature<SimpleGeometr
 
         const color = getOverlayBioEntityColorByAvailableProperties(entity);
 
+        if (entity.type === 'submap-link') {
+          return createOverlaySubmapLinkRectangleFeature(
+            [
+              ...pointToProjection({ x: xMin, y: entity.y1 }),
+              ...pointToProjection({ x: xMax, y: entity.y2 }),
+            ],
+            entity.value === Infinity ? null : color,
+          );
+        }
+
         if (entity.type === 'rectangle') {
           return createOverlayGeometryFeature(
             [
@@ -60,7 +70,7 @@ export const useOverlayFeatures = (): Feature<Polygon>[] | Feature<SimpleGeometr
           },
         );
       }),
-    [overlaysOrder, bioEntities, pointToProjection, getOverlayBioEntityColorByAvailableProperties],
+    [overlaysOrder, pointToProjection, getOverlayBioEntityColorByAvailableProperties, bioEntities],
   );
 
   return features;
diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts
index ac815f25ece6507d4ad61f5ebc2a8bc87dc6e1f9..f7816e937ba3aa3631e79975ef7e7fa48c48e93b 100644
--- a/src/redux/apiPath.ts
+++ b/src/redux/apiPath.ts
@@ -51,7 +51,7 @@ export const apiPath = {
   getConfigurationOptions: (): string => 'configuration/options/',
   getConfiguration: (): string => 'configuration/',
   getOverlayBioEntity: ({ overlayId, modelId }: { overlayId: number; modelId: number }): string =>
-    `projects/${PROJECT_ID}/overlays/${overlayId}/models/${modelId}/bioEntities/`,
+    `projects/${PROJECT_ID}/overlays/${overlayId}/models/${modelId}/bioEntities/?includeIndirect=true`,
   createOverlay: (projectId: string): string => `projects/${projectId}/overlays/`,
   createOverlayFile: (): string => `files/`,
   uploadOverlayFileContent: (fileId: number): string => `files/${fileId}:uploadContent`,
diff --git a/src/redux/overlayBioEntity/overlayBioEntity.mock.ts b/src/redux/overlayBioEntity/overlayBioEntity.mock.ts
index 9b093c3eeaecee77b8fc2923f37f8cd3da8c5eec..bd2d8522533e66f539c0cd656fee9ab239ea2202 100644
--- a/src/redux/overlayBioEntity/overlayBioEntity.mock.ts
+++ b/src/redux/overlayBioEntity/overlayBioEntity.mock.ts
@@ -78,3 +78,159 @@ export const MOCKED_OVERLAY_BIO_ENTITY_RENDER: OverlayBioEntityRender[] = [
     color: null,
   },
 ];
+
+export const MOCKED_OVERLAY_SUBMAPS_LINKS_WITH_SAME_COLORS: OverlayBioEntityRender[] = [
+  {
+    type: 'rectangle',
+    id: 1,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 3128.653195488725,
+    y2: 3088.653195488725,
+    overlayId: 12,
+    height: 10,
+    value: 0,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 100,
+    value: 23,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 100,
+    value: 23,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 100,
+    value: null,
+    color: {
+      alpha: 255,
+      rgb: -2348283,
+    },
+  },
+  {
+    type: 'submap-link',
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 100,
+    value: null,
+    color: {
+      alpha: 255,
+      rgb: -2348283,
+    },
+  },
+];
+
+export const MOCKED_OVERLAY_SUBMAPS_LINKS_WITH_DIFFERENT_COLORS: OverlayBioEntityRender[] = [
+  {
+    type: 'rectangle',
+    id: 1,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 3128.653195488725,
+    y2: 3088.653195488725,
+    overlayId: 12,
+    height: 10,
+    value: 0,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 100,
+    value: 0.4,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 100,
+    value: 0.5,
+    color: null,
+  },
+  {
+    type: 'submap-link',
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 100,
+    value: 0.8,
+    color: null,
+  },
+
+  {
+    type: 'submap-link',
+    id: 97,
+    modelId: 52,
+    width: 30,
+    x1: 18412,
+    x2: 18492,
+    y1: 2100.5,
+    y2: 2000.5,
+    overlayId: 12,
+    height: 100,
+    value: null,
+    color: {
+      alpha: 255,
+      rgb: -2348283,
+    },
+  },
+];
diff --git a/src/redux/overlayBioEntity/overlayBioEntity.utils.ts b/src/redux/overlayBioEntity/overlayBioEntity.utils.ts
index b765ff8de4471783e7eca9ffa73ec405b9829118..becf2d2ab94699294879668994a6b5c88cf73f62 100644
--- a/src/redux/overlayBioEntity/overlayBioEntity.utils.ts
+++ b/src/redux/overlayBioEntity/overlayBioEntity.utils.ts
@@ -3,7 +3,7 @@ import { overlayBioEntitySchema } from '@/models/overlayBioEntitySchema';
 import { OverlayBioEntityRender } from '@/types/OLrendering';
 import { OverlayBioEntity } from '@/types/models';
 import { getOverlayReactionCoordsFromLine } from '@/utils/overlays/getOverlayReactionCoords';
-import { isBioEntity, isReaction } from '@/utils/overlays/overlaysElementsTypeGuards';
+import { isBioEntity, isReaction, isSubmapLink } from '@/utils/overlays/overlaysElementsTypeGuards';
 import { z } from 'zod';
 
 export const parseOverlayBioEntityToOlRenderingFormat = (
@@ -18,6 +18,24 @@ export const parseOverlayBioEntityToOlRenderingFormat = (
      * Every reaction line is a different entity after reduce
      */
 
+    if (isSubmapLink(entity)) {
+      acc.push({
+        type: 'submap-link',
+        id: entity.left.id,
+        modelId: entity.left.model,
+        x1: entity.left.x,
+        y1: entity.left.y + entity.left.height,
+        x2: entity.left.x + entity.left.width,
+        y2: entity.left.y,
+        width: entity.left.width,
+        height: entity.left.height,
+        value: entity.right.value,
+        overlayId,
+        color: entity.right.color,
+      });
+      return acc;
+    }
+
     if (isBioEntity(entity)) {
       acc.push({
         type: 'rectangle',
diff --git a/src/types/OLrendering.ts b/src/types/OLrendering.ts
index 18075b437e4f2f7e91f7de2cbe1955f8851feaf4..9769064d9bada810382fe653e7df432030f9f94b 100644
--- a/src/types/OLrendering.ts
+++ b/src/types/OLrendering.ts
@@ -1,6 +1,6 @@
 import { Color, GeneVariant } from './models';
 
-export type OverlayBioEntityRenderType = 'line' | 'rectangle';
+export type OverlayBioEntityRenderType = 'line' | 'rectangle' | 'submap-link';
 
 export type OverlayBioEntityRender = {
   id: number;
diff --git a/src/utils/overlays/overlaysElementsTypeGuards.ts b/src/utils/overlays/overlaysElementsTypeGuards.ts
index 6997b141f5d627b5d4641ee23c2f7b902f17a1af..fdd3f1938b4021b29c610671c6e5225b6b97d082 100644
--- a/src/utils/overlays/overlaysElementsTypeGuards.ts
+++ b/src/utils/overlays/overlaysElementsTypeGuards.ts
@@ -12,3 +12,8 @@ export const isReaction = (e: OverlayBioEntity): e is OverlayElementWithReaction
 export const isBioEntity = (e: OverlayBioEntity): e is OverlayElementWithBioEntity =>
   (e.left as OverlayLeftBioEntity).x !== undefined &&
   (e.left as OverlayLeftBioEntity).y !== undefined;
+
+export const isSubmapLink = (e: OverlayBioEntity): e is OverlayElementWithBioEntity =>
+  (e.left as OverlayLeftBioEntity).x !== undefined &&
+  (e.left as OverlayLeftBioEntity).y !== undefined &&
+  (e.left as OverlayLeftBioEntity).submodel !== undefined;