From 14f5a91fc084943d14688185969165e4d3aca420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl> Date: Wed, 11 Dec 2024 13:35:09 +0100 Subject: [PATCH 1/9] feat(vector-map): add duration to mouseWheelZoom --- .../utils/config/reactionsLayer/useOlMapReactionsLayer.ts | 2 ++ src/components/Map/MapViewer/utils/useOlMap.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) 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 bf0d3389..86787f11 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts @@ -244,6 +244,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/utils/useOlMap.ts b/src/components/Map/MapViewer/utils/useOlMap.ts index 23d585e0..68ec65bd 100644 --- a/src/components/Map/MapViewer/utils/useOlMap.ts +++ b/src/components/Map/MapViewer/utils/useOlMap.ts @@ -65,7 +65,7 @@ export const useOlMap: UseOlMap = ({ target } = {}) => { mouseWheelZoom: false, }).extend([ new MouseWheelZoom({ - duration: 0, + duration: 250, timeout: 80, }), ]), -- GitLab From 7d8aec25002cdac1856dad1a48af5bb303ee6a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl> Date: Wed, 11 Dec 2024 13:38:19 +0100 Subject: [PATCH 2/9] bugfix(vector-map): add overlaysOrder state in useOlMapReactionsLayer to avoid elements reloading --- .../reactionsLayer/useOlMapReactionsLayer.ts | 19 +++++++++--- .../utils/shapes/elements/BaseMultiPolygon.ts | 2 +- .../shapes/overlay/areOverlayOrdersEqual.ts | 31 +++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/areOverlayOrdersEqual.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 86787f11..509d5cbb 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts @@ -2,7 +2,7 @@ import { Feature } from 'ol'; import VectorLayer from 'ol/layer/Vector'; import VectorSource from 'ol/source/Vector'; -import { useEffect, useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { usePointToProjection } from '@/utils/map/usePointToProjection'; import MapElement from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement'; import { useSelector } from 'react-redux'; @@ -48,6 +48,8 @@ import { mapBackgroundTypeSelector, mapDataSizeSelector } from '@/redux/map/map. import MapBackgroundsEnum from '@/redux/map/map.enums'; import { setMapBackgroundType } from '@/redux/map/map.slice'; import { ZOOM_RESCALING_FACTOR } from '@/constants/map'; +import { OverlayOrder } from '@/redux/overlayBioEntity/overlayBioEntity.utils'; +import areOverlayOrdersEqual from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/areOverlayOrdersEqual'; export const useOlMapReactionsLayer = ({ mapInstance, @@ -56,6 +58,7 @@ export const useOlMapReactionsLayer = ({ }): VectorLayer<VectorSource<Feature>> => { const dispatch = useAppDispatch(); + const [overlaysOrderState, setOverlaysOrderState] = useState<Array<OverlayOrder>>([]); const currentModelId = useSelector(currentModelIdSelector); const shapes = useSelector(bioShapesSelector); const mapSize = useSelector(mapDataSizeSelector); @@ -84,6 +87,12 @@ export const useOlMapReactionsLayer = ({ return mapSize.maxZoom * ZOOM_RESCALING_FACTOR === mapModelOriginalMaxZoom; }, [mapModelOriginalMaxZoom, mapSize.maxZoom]); + useEffect(() => { + if (areOverlayOrdersEqual(overlaysOrderState, overlaysOrder)) { + setOverlaysOrderState(overlaysOrder); + } + }, [overlaysOrder, overlaysOrderState]); + useEffect(() => { if (!currentModelId) { return; @@ -97,10 +106,10 @@ export const useOlMapReactionsLayer = ({ }, [currentModelId, dispatch, reactionsLoading, modelElementsLoading]); useEffect(() => { - if (overlaysOrder.length) { + if (overlaysOrderState.length) { dispatch(setMapBackgroundType(MapBackgroundsEnum.NETWORK)); } - }, [dispatch, overlaysOrder]); + }, [dispatch, overlaysOrderState]); const groupedElementsOverlays = useMemo(() => { const elementsBioEntitesOverlay = debouncedBioEntities.filter( @@ -202,7 +211,7 @@ export const useOlMapReactionsLayer = ({ shapes, lineTypes, groupedElementsOverlays, - overlaysOrder, + overlaysOrderState, getOverlayBioEntityColorByAvailableProperties, vectorSource, mapInstance, @@ -216,7 +225,7 @@ export const useOlMapReactionsLayer = ({ isCorrectMapInstanceViewScale, lineTypes, groupedElementsOverlays, - overlaysOrder, + overlaysOrderState, getOverlayBioEntityColorByAvailableProperties, vectorSource, mapInstance, 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 c13c61f4..30f48a79 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts @@ -309,7 +309,7 @@ export default abstract class BaseMultiPolygon { largestExtent, this.text, scale, - this.zIndex + 1000, + this.zIndex + 100000, this.mapSize, ), ); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/areOverlayOrdersEqual.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/areOverlayOrdersEqual.ts new file mode 100644 index 00000000..965ef019 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/overlay/areOverlayOrdersEqual.ts @@ -0,0 +1,31 @@ +/* eslint-disable no-magic-numbers */ +import { OverlayOrder } from '@/redux/overlayBioEntity/overlayBioEntity.utils'; + +export default function areOverlayOrdersEqual( + overlaysOrder1: Array<OverlayOrder>, + overlaysOrder2: Array<OverlayOrder>, +): boolean { + if (overlaysOrder1 === overlaysOrder2) { + return true; + } + + if (overlaysOrder1.length !== overlaysOrder2.length) { + return false; + } + + for (let index = 0; index < overlaysOrder1.length; index += 1) { + const obj1 = overlaysOrder1[index]; + const obj2 = overlaysOrder2[index]; + + if ( + obj1.id !== obj2.id || + obj1.order !== obj2.order || + obj1.calculatedOrder !== obj2.calculatedOrder || + obj1.index !== obj2.index + ) { + return false; + } + } + + return true; +} -- GitLab From 20bd0d613a953091ecf1ec3e23870dd68001dd48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl> Date: Wed, 11 Dec 2024 14:46:42 +0100 Subject: [PATCH 3/9] feat(vector-map): add semantic view as a default and stop masking content of elemenets when text labels appear --- .../utils/shapes/elements/BaseMultiPolygon.ts | 15 ++--------- .../utils/shapes/elements/MapElement.ts | 3 --- .../elements/handleSemanticView.test.ts | 27 +++++-------------- .../shapes/elements/handleSemanticView.ts | 3 +-- .../utils/shapes/reaction/Reaction.ts | 3 +-- src/redux/map/map.constants.ts | 2 +- 6 files changed, 12 insertions(+), 41 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 30f48a79..fd2f74fd 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts @@ -196,8 +196,6 @@ export default abstract class BaseMultiPolygon { }); const textPolygon = new Polygon([[textCoords, textCoords]]); textPolygon.set('type', MAP_ELEMENT_TYPES.TEXT); - textPolygon.set('text', this.text); - textPolygon.set('fontSize', this.fontSize); const textStyle = getTextStyle({ text: this.text, fontSize: this.fontSize, @@ -267,12 +265,11 @@ export default abstract class BaseMultiPolygon { feature.set('hidden', false); let hide = false; - if (this.mapBackgroundType === MapBackgroundsEnum.SEMANTIC) { + if (this.mapBackgroundType === MapBackgroundsEnum.SEMANTIC && scale < 0.34) { const semanticViewData = handleSemanticView( this.vectorSource, feature, resolution, - scale, this.compartmentId, this.complexId, ); @@ -286,8 +283,6 @@ export default abstract class BaseMultiPolygon { } let type: string; - let fontSize: number; - let text: string; let coverStyle: Style | undefined; let strokeStyle: Stroke | undefined; @@ -295,8 +290,6 @@ export default abstract class BaseMultiPolygon { const styleGeometry = style.getGeometry(); if (styleGeometry instanceof Polygon) { type = styleGeometry.get('type'); - text = styleGeometry.get('text'); - fontSize = styleGeometry.get('fontSize') || 10; coverStyle = styleGeometry.get('coverStyle'); strokeStyle = styleGeometry.get('strokeStyle'); } @@ -317,17 +310,13 @@ export default abstract class BaseMultiPolygon { return; } - if ( - [MAP_ELEMENT_TYPES.MODIFICATION, MAP_ELEMENT_TYPES.TEXT].includes(type) && - scale * fontSize <= 4 - ) { + if ([MAP_ELEMENT_TYPES.MODIFICATION, MAP_ELEMENT_TYPES.TEXT].includes(type) && scale < 0.34) { return; } const textStyle = style.getText(); if (type === 'text' && textStyle) { textStyle.setScale(scale); - textStyle.setText(text); } if (strokeStyle) { const lineWidth = strokeStyle.getWidth() || 1; 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 77ab7ebc..15a15c3e 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts @@ -221,7 +221,6 @@ export default class MapElement extends BaseMultiPolygon { mirror: modification.direction && modification.direction === 'RIGHT', }); modificationPolygon.set('type', MAP_ELEMENT_TYPES.MODIFICATION); - modificationPolygon.set('fontSize', modification.fontSize); const modificationStrokeStyle = getStroke({ color: rgbToHex(modification.borderColor) }); const modificationStyle = new Style({ geometry: modificationPolygon, @@ -252,8 +251,6 @@ export default class MapElement extends BaseMultiPolygon { [modificationTextCoords, modificationTextCoords], ]); modificationTextPolygon.set('type', MAP_ELEMENT_TYPES.TEXT); - modificationTextPolygon.set('text', modificationText); - modificationTextPolygon.set('fontSize', modification.fontSize); const modificationTextStyle = getTextStyle({ text: modificationText, fontSize: modification.fontSize, 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 index 0b7e01e1..3c4ae01b 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.test.ts @@ -50,7 +50,7 @@ describe('handleSemanticView', () => { (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); + const result = handleSemanticView(vectorSource, feature, 1, null); expect(result).toEqual({ cover: true, @@ -69,12 +69,12 @@ describe('handleSemanticView', () => { .spyOn(vectorSource, 'getFeatureById') .mockImplementation(id => (id === 1 ? complexFeature : null)); - const result = handleSemanticView(vectorSource, feature, 1, 1, null, 1); + const result = handleSemanticView(vectorSource, feature, 1, null, 1); expect(result).toEqual({ - cover: false, + cover: true, hide: true, - largestExtent: null, + largestExtent: [0, 0, 10, 5], }); expect(feature.get('hidden')).toBe(true); @@ -86,27 +86,14 @@ describe('handleSemanticView', () => { .spyOn(vectorSource, 'getFeatureById') .mockImplementation(id => (id === 2 ? compartmentFeature : null)); - const result = handleSemanticView(vectorSource, feature, 1, 1, 2); + const result = handleSemanticView(vectorSource, feature, 1, 2); expect(result).toEqual({ - cover: false, + cover: true, hide: true, - largestExtent: null, + largestExtent: [0, 0, 10, 5], }); 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 index 83269b75..19619484 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts @@ -9,7 +9,6 @@ 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 } { @@ -28,7 +27,7 @@ export default function handleSemanticView( Math.abs(featureExtent[2] - featureExtent[0]) * Math.abs(featureExtent[3] - featureExtent[1]); coverRatio = compartmentArea / mapArea; - if (coverRatio < 0.05 && scale < 1) { + if (coverRatio < 0.05) { cover = true; let remainingExtents = [featureExtent]; vectorSource.forEachFeatureIntersectingExtent(featureExtent, intersectingFeature => { 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 afeeeeab..0a611adc 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts @@ -364,10 +364,9 @@ export default class Reaction { const styles: Array<Style> = []; const scale = this.minResolution / resolution; const type = feature.get('elementType'); - const fontSize = feature.get('fontSize'); let strokeStyle: Stroke | undefined; - if (type === REACTION_ELEMENT_TYPES.OPERATOR && fontSize * scale <= 4) { + if (type === REACTION_ELEMENT_TYPES.OPERATOR && scale < 0.34) { return []; } if (type === REACTION_ELEMENT_TYPES.ARROW && scale <= 0.125) { diff --git a/src/redux/map/map.constants.ts b/src/redux/map/map.constants.ts index 90648de6..55b67d50 100644 --- a/src/redux/map/map.constants.ts +++ b/src/redux/map/map.constants.ts @@ -73,7 +73,7 @@ export const MAP_INITIAL_STATE: MapState = { loading: 'idle', error: { name: '', message: '' }, openedMaps: OPENED_MAPS_INITIAL_STATE, - backgroundType: MapBackgroundsEnum.NETWORK, + backgroundType: MapBackgroundsEnum.SEMANTIC, }; export const INIT_MAP_SIZE_MODEL_ID_ERROR_PREFIX = 'Failed to initialize map size and model ID'; -- GitLab From fe7eacb7198cfa86ad9318a629bde5aaad51a0ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl> Date: Wed, 11 Dec 2024 15:48:24 +0100 Subject: [PATCH 4/9] feat(vector-map): treating complexes in the same way as compartments --- .../utils/shapes/elements/BaseMultiPolygon.ts | 37 +++++++------------ .../utils/shapes/elements/MapElement.ts | 23 ++++++++---- .../elements/handleSemanticView.test.ts | 25 +++++++++++-- .../shapes/elements/handleSemanticView.ts | 30 ++++++++++----- .../utils/shapes/style/getCoverStyles.test.ts | 4 +- .../utils/shapes/style/getCoverStyles.ts | 32 ++++++++++++---- .../style/getScaledElementStyle.test.ts | 36 ++++++++++++++++++ .../shapes/style/getScaledElementStyle.ts | 14 +------ .../shapes/style/getScaledStrokeStyle.test.ts | 35 ++++++++++++++++++ .../shapes/style/getScaledStrokeStyle.ts | 16 ++++++++ 10 files changed, 187 insertions(+), 65 deletions(-) create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.test.ts create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle.test.ts create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle.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 fd2f74fd..a771638e 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts @@ -23,6 +23,7 @@ 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'; +import getScaledStrokeStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle'; export interface BaseMapElementProps { type: string; @@ -266,13 +267,14 @@ export default abstract class BaseMultiPolygon { let hide = false; if (this.mapBackgroundType === MapBackgroundsEnum.SEMANTIC && scale < 0.34) { - const semanticViewData = handleSemanticView( - this.vectorSource, + const semanticViewData = handleSemanticView({ + vectorSource: this.vectorSource, feature, resolution, - this.compartmentId, - this.complexId, - ); + sboTerm: this.sboTerm, + compartmentId: this.compartmentId, + complexId: this.complexId, + }); cover = semanticViewData.cover; hide = semanticViewData.hide; largestExtent = semanticViewData.largestExtent; @@ -297,14 +299,15 @@ export default abstract class BaseMultiPolygon { if (cover) { if (coverStyle && largestExtent) { styles.push( - ...getCoverStyles( + ...getCoverStyles({ coverStyle, largestExtent, - this.text, + text: this.text, scale, - this.zIndex + 100000, - this.mapSize, - ), + zIndex: this.zIndex + 100000, + mapSize: this.mapSize, + strokeStyle, + }), ); } return; @@ -319,7 +322,6 @@ export default abstract class BaseMultiPolygon { textStyle.setScale(scale); } if (strokeStyle) { - const lineWidth = strokeStyle.getWidth() || 1; if ( !this.overlaysVisible && scale < 0.18 && @@ -328,18 +330,7 @@ export default abstract class BaseMultiPolygon { ) { style.setStroke(null); } else { - const lineDash = strokeStyle.getLineDash(); - let newLineDash: Array<number> = []; - if (lineDash) { - newLineDash = lineDash.map(width => width * scale); - } - const newStrokeStyle = new Stroke({ - color: strokeStyle.getColor(), - width: lineWidth * scale, - lineDash: newLineDash, - }); - - style.setStroke(newStrokeStyle); + style.setStroke(getScaledStrokeStyle(strokeStyle, scale)); } } styles.push(style); 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 15a15c3e..a8ee5425 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, + COMPLEX_SBO_TERMS, MAP_ELEMENT_TYPES, TRANSPARENT_COLOR, WHITE_COLOR, @@ -311,14 +312,20 @@ export default class MapElement extends BaseMultiPolygon { lineDash: this.lineDash, zIndex: this.zIndex, }); - elementPolygon.set( - 'strokeStyle', - getStroke({ - color: rgbToHex(this.borderColor), - width: this.lineWidth, - lineDash: this.lineDash, - }), - ); + const strokeStyle = getStroke({ + color: rgbToHex(this.borderColor), + width: this.lineWidth, + lineDash: this.lineDash, + }); + elementPolygon.set('strokeStyle', strokeStyle); + if (COMPLEX_SBO_TERMS.includes(this.sboTerm)) { + const coverStyle = new Style({ + geometry: elementPolygon, + fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 255 }) }), + stroke: strokeStyle, + }); + elementPolygon.set('coverStyle', coverStyle); + } this.polygons.push(elementPolygon); this.styles.push(elementStyle); }); 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 index 3c4ae01b..ddcf4df5 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.test.ts @@ -50,7 +50,13 @@ describe('handleSemanticView', () => { (getDividedExtents as jest.Mock).mockReturnValue([[0, 0, 10, 5]]); (findLargestExtent as jest.Mock).mockReturnValue([0, 0, 10, 5]); - const result = handleSemanticView(vectorSource, feature, 1, null); + const result = handleSemanticView({ + vectorSource, + feature, + resolution: 1, + sboTerm: 'SBO:123456', + compartmentId: null, + }); expect(result).toEqual({ cover: true, @@ -69,7 +75,14 @@ describe('handleSemanticView', () => { .spyOn(vectorSource, 'getFeatureById') .mockImplementation(id => (id === 1 ? complexFeature : null)); - const result = handleSemanticView(vectorSource, feature, 1, null, 1); + const result = handleSemanticView({ + vectorSource, + feature, + resolution: 1, + sboTerm: 'SBO:123456', + compartmentId: null, + complexId: 1, + }); expect(result).toEqual({ cover: true, @@ -86,7 +99,13 @@ describe('handleSemanticView', () => { .spyOn(vectorSource, 'getFeatureById') .mockImplementation(id => (id === 2 ? compartmentFeature : null)); - const result = handleSemanticView(vectorSource, feature, 1, 2); + const result = handleSemanticView({ + vectorSource, + feature, + resolution: 1, + sboTerm: 'SBO:123456', + compartmentId: 2, + }); expect(result).toEqual({ cover: true, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts index 19619484..a15bc312 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts @@ -4,21 +4,33 @@ import findLargestExtent from '@/components/Map/MapViewer/MapViewerVector/utils/ import Feature from 'ol/Feature'; import VectorSource from 'ol/source/Vector'; import { Extent } from 'ol/extent'; +import { COMPLEX_SBO_TERMS } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; -export default function handleSemanticView( - vectorSource: VectorSource, - feature: Feature, - resolution: number, - compartmentId: number | null, - complexId?: number | null, -): { cover: boolean; hide: boolean; largestExtent: Extent | null } { +export default function handleSemanticView({ + vectorSource, + feature, + resolution, + sboTerm, + compartmentId, + complexId, +}: { + vectorSource: VectorSource; + feature: Feature; + resolution: number; + sboTerm: string; + 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') { + if ( + getMapExtent instanceof Function && + (type === 'COMPARTMENT' || COMPLEX_SBO_TERMS.includes(sboTerm)) + ) { const mapExtent = getMapExtent(resolution); const featureExtent = feature.getGeometry()?.getExtent(); if (featureExtent && mapExtent) { @@ -51,7 +63,7 @@ export default function handleSemanticView( if (complexId) { const complex = vectorSource.getFeatureById(complexId); - if (complex && complex.get('hidden')) { + if (complex && (complex.get('hidden') || complex.get('filled'))) { hide = true; } } 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 index 0e22293c..8178ab4a 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.test.ts @@ -39,7 +39,7 @@ describe('getCoverStyles', () => { const mockTextStyle = new Style(); (getTextStyle as jest.Mock).mockReturnValue(mockTextStyle); - const result = getCoverStyles(coverStyle, largestExtent, text, scale, zIndex, mapSize); + const result = getCoverStyles({ coverStyle, largestExtent, text, scale, zIndex, mapSize }); expect(result).toHaveLength(2); expect(result[0]).toBe(coverStyle); @@ -80,7 +80,7 @@ describe('getCoverStyles', () => { fontSize: 0, }); - const result = getCoverStyles(coverStyle, largestExtent, text, scale, zIndex, mapSize); + 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 index ce072c86..78f2ef6b 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getCoverStyles.ts @@ -7,17 +7,33 @@ import getWrappedTextWithFontSize from '@/components/Map/MapViewer/MapViewerVect import { Point } from 'ol/geom'; import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle'; import { MapSize } from '@/redux/map/map.types'; +import { Stroke } from 'ol/style'; +import getScaledStrokeStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle'; -export default function getCoverStyles( - coverStyle: Style, - largestExtent: Extent, - text: string, - scale: number, - zIndex: number, - mapSize: MapSize, -): Array<Style> { +export default function getCoverStyles({ + coverStyle, + largestExtent, + text, + scale, + zIndex, + mapSize, + strokeStyle, +}: { + coverStyle: Style; + largestExtent: Extent; + text: string; + scale: number; + zIndex: number; + mapSize: MapSize; + strokeStyle?: Stroke; +}): Array<Style> { const styles: Array<Style> = []; coverStyle.setZIndex(zIndex); + + if (coverStyle.getStroke() && strokeStyle) { + coverStyle.setStroke(getScaledStrokeStyle(strokeStyle, scale)); + } + styles.push(coverStyle); if (text) { diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.test.ts new file mode 100644 index 00000000..5c22d994 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.test.ts @@ -0,0 +1,36 @@ +/* eslint-disable no-magic-numbers */ +import Style from 'ol/style/Style'; +import { Stroke, Text } from 'ol/style'; +import getScaledStrokeStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle'; +import getScaledElementStyle from './getScaledElementStyle'; + +jest.mock('./getScaledStrokeStyle'); + +describe('getScaledElementStyle', () => { + it('should scale the stroke style and text scale when strokeStyle is provided', () => { + const mockScaledStroke = new Stroke({ color: 'blue', width: 4 }); + (getScaledStrokeStyle as jest.Mock).mockReturnValue(mockScaledStroke); + + const strokeStyle = new Stroke({ color: 'red', width: 2 }); + const textStyle = new Text({ text: 'Test', scale: 1 }); + const style = new Style({ stroke: strokeStyle, text: textStyle }); + const scale = 2; + + const scaledStyle = getScaledElementStyle(style, strokeStyle, scale); + + expect(getScaledStrokeStyle).toHaveBeenCalledWith(strokeStyle, scale); + expect(scaledStyle.getStroke()).toBe(mockScaledStroke); + expect(scaledStyle.getText()?.getScale()).toBe(2); + }); + + it('should scale only text when strokeStyle is not provided', () => { + const textStyle = new Text({ text: 'Test', scale: 1 }); + const style = new Style({ text: textStyle }); + const scale = 3; + + const scaledStyle = getScaledElementStyle(style, undefined, scale); + + expect(scaledStyle.getStroke()).toBeNull(); + expect(scaledStyle.getText()?.getScale()).toBe(3); + }); +}); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts index 27c0c515..a91c0309 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts @@ -1,6 +1,7 @@ /* eslint-disable no-magic-numbers */ import Style from 'ol/style/Style'; import { Stroke } from 'ol/style'; +import getScaledStrokeStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle'; export default function getScaledElementStyle( style: Style, @@ -8,18 +9,7 @@ export default function getScaledElementStyle( scale: number, ): Style { if (strokeStyle) { - const lineDash = strokeStyle.getLineDash(); - let newLineDash: Array<number> = []; - if (lineDash) { - newLineDash = lineDash.map(width => width * scale); - } - const newStrokeStyle = new Stroke({ - color: strokeStyle.getColor(), - width: (strokeStyle.getWidth() || 1) * scale, - lineDash: newLineDash, - }); - - style.setStroke(newStrokeStyle); + style.setStroke(getScaledStrokeStyle(strokeStyle, scale)); } style.getText()?.setScale(scale); return style; diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle.test.ts new file mode 100644 index 00000000..759b88e5 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle.test.ts @@ -0,0 +1,35 @@ +/* eslint-disable no-magic-numbers */ +import { Stroke } from 'ol/style'; +import getScaledStrokeStyle from './getScaledStrokeStyle'; + +describe('getScaledStrokeStyle', () => { + it('should correctly scale the width and lineDash array of a Stroke style', () => { + const strokeStyle = new Stroke({ + color: 'red', + width: 2, + lineDash: [4, 8], + }); + const scale = 0.2; + + const scaledStroke = getScaledStrokeStyle(strokeStyle, scale); + + expect(scaledStroke.getColor()).toBe('red'); + expect(scaledStroke.getWidth()).toBe(0.4); + expect(scaledStroke.getLineDash()).toEqual([0.8, 1.6]); + }); + + it('should use a default width of 1 and scale it correctly when getWidth() returns undefined', () => { + const strokeStyle = new Stroke({ + color: 'green', + lineDash: [2, 3], + }); + jest.spyOn(strokeStyle, 'getWidth').mockReturnValue(undefined); + const scale = 3; + + const scaledStroke = getScaledStrokeStyle(strokeStyle, scale); + + expect(scaledStroke.getColor()).toBe('green'); + expect(scaledStroke.getWidth()).toBe(3); + expect(scaledStroke.getLineDash()).toEqual([6, 9]); + }); +}); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle.ts new file mode 100644 index 00000000..111adc06 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledStrokeStyle.ts @@ -0,0 +1,16 @@ +/* eslint-disable no-magic-numbers */ +import { Stroke } from 'ol/style'; + +export default function getScaledStrokeStyle(strokeStyle: Stroke, scale: number): Stroke { + const lineWidth = strokeStyle.getWidth() || 1; + const lineDash = strokeStyle.getLineDash(); + let newLineDash: Array<number> = []; + if (lineDash) { + newLineDash = lineDash.map(width => width * scale); + } + return new Stroke({ + color: strokeStyle.getColor(), + width: lineWidth * scale, + lineDash: newLineDash, + }); +} -- GitLab From 03ab127f1065963596916b20d519265fbbfe2de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl> Date: Thu, 12 Dec 2024 12:02:16 +0100 Subject: [PATCH 5/9] fix(semantic-view): remove attribute 'hidden' from features to optimize styling function --- .../mouseClick/mouseLeftClick/onMapLeftClick.ts | 3 +-- .../mouseClick/mouseRightClick/onMapRightClick.ts | 2 +- .../reactionsLayer/useOlMapReactionsLayer.ts | 5 ++++- .../utils/shapes/elements/BaseMultiPolygon.ts | 2 -- .../shapes/elements/handleSemanticView.test.ts | 12 +++++------- .../utils/shapes/elements/handleSemanticView.ts | 7 +++---- .../utils/shapes/reaction/Reaction.ts | 14 +++++++++----- 7 files changed, 23 insertions(+), 22 deletions(-) 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 62ad872d..3a33837b 100644 --- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts @@ -54,8 +54,7 @@ export const onMapLeftClick = const featureZIndex = feature.get('zIndex'); if ( (isFeatureFilledCompartment(feature) || isFeatureNotCompartment(feature)) && - (featureZIndex === undefined || featureZIndex >= 0) && - !feature.get('hidden') + (featureZIndex === undefined || featureZIndex >= 0) ) { featureAtPixel = feature; return true; 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 2822685f..74e10fc2 100644 --- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts @@ -40,7 +40,7 @@ export const onMapRightClick = FEATURE_TYPE.REACTION, FEATURE_TYPE.GLYPH ].includes(feature.get('type')) - ) && feature.get('zIndex') >= 0 && !feature.get('hidden'); + ) && feature.get('zIndex') >= 0; }); } } 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 509d5cbb..adef459c 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts @@ -67,7 +67,10 @@ export const useOlMapReactionsLayer = ({ const overlaysOrder = useSelector(getOverlayOrderSelector); const mapBackgroundType = useSelector(mapBackgroundTypeSelector); const currentMarkers = useAppSelector(markersSufraceOfCurrentMapDataSelector); - const markersRender = parseSurfaceMarkersToBioEntityRender(currentMarkers); + const markersRender = useMemo(() => { + return parseSurfaceMarkersToBioEntityRender(currentMarkers); + }, [currentMarkers]); + const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector); const reactionsForCurrentModel = useAppSelector(newReactionsForCurrentModelSelector); const modelElementsLoading = useAppSelector(modelElementsLoadingSelector); 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 a771638e..3a100b05 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts @@ -260,10 +260,8 @@ export default abstract class BaseMultiPolygon { let largestExtent: Extent | null; if (this.complexId && !COMPLEX_SBO_TERMS.includes(this.sboTerm) && scale < 0.215) { - feature.set('hidden', true); return []; } - feature.set('hidden', false); let hide = false; if (this.mapBackgroundType === MapBackgroundsEnum.SEMANTIC && scale < 0.34) { 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 index ddcf4df5..f6360b51 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.test.ts @@ -24,6 +24,7 @@ describe('handleSemanticView', () => { filled: false, hidden: false, }); + feature.setId(1); const mockGeometry = { getExtent: jest.fn(() => [2, 0, 10, 10]), @@ -38,6 +39,7 @@ describe('handleSemanticView', () => { .mockImplementation((_, callback) => { callback( new Feature({ + id: 1, geometry: fromExtent([1, 0, 5, 5]), hidden: false, type: 'COMPARTMENT', @@ -55,7 +57,7 @@ describe('handleSemanticView', () => { feature, resolution: 1, sboTerm: 'SBO:123456', - compartmentId: null, + compartmentId: 2, }); expect(result).toEqual({ @@ -69,8 +71,8 @@ describe('handleSemanticView', () => { expect(findLargestExtent).toHaveBeenCalled(); }); - it('should return hide = true when complexId points to a hidden feature', () => { - const complexFeature = new Feature({ hidden: true }); + it('should return hide = true when complexId points to a filled feature', () => { + const complexFeature = new Feature({ filled: true }); jest .spyOn(vectorSource, 'getFeatureById') .mockImplementation(id => (id === 1 ? complexFeature : null)); @@ -89,8 +91,6 @@ describe('handleSemanticView', () => { hide: true, largestExtent: [0, 0, 10, 5], }); - - expect(feature.get('hidden')).toBe(true); }); it('should return hide = true when compartmentId points to a filled feature', () => { @@ -112,7 +112,5 @@ describe('handleSemanticView', () => { hide: true, largestExtent: [0, 0, 10, 5], }); - - expect(feature.get('hidden')).toBe(true); }); }); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts index a15bc312..d1007e7b 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts @@ -44,10 +44,10 @@ export default function handleSemanticView({ let remainingExtents = [featureExtent]; vectorSource.forEachFeatureIntersectingExtent(featureExtent, intersectingFeature => { if ( - !intersectingFeature.get('hidden') && intersectingFeature.get('type') === 'COMPARTMENT' && intersectingFeature.get('zIndex') > feature.get('zIndex') && - intersectingFeature.get('filled') + intersectingFeature.get('filled') && + intersectingFeature.get('compartmentId') !== feature.getId() ) { const intersectingFeatureExtent = intersectingFeature.getGeometry()?.getExtent(); if (intersectingFeatureExtent) { @@ -63,7 +63,7 @@ export default function handleSemanticView({ if (complexId) { const complex = vectorSource.getFeatureById(complexId); - if (complex && (complex.get('hidden') || complex.get('filled'))) { + if (complex && complex.get('filled')) { hide = true; } } @@ -73,7 +73,6 @@ export default function handleSemanticView({ hide = true; } } - (feature as Feature).set('hidden', hide); return { cover, hide, largestExtent }; } 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 0a611adc..e6e2de63 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts @@ -347,7 +347,15 @@ export default class Reaction { protected isAnyOfElementsHidden(): boolean { return [...this.products, ...this.reactants, ...this.modifiers].some(reactionElement => { const feature = this.vectorSource.getFeatureById(reactionElement.element); - return feature && feature.get('hidden'); + if (!feature) { + return false; + } + const complexId: undefined | number = feature.get('complexId'); + const compartmentId: undefined | null | number = feature.get('compartmentId'); + if (complexId && this.vectorSource.getFeatureById(complexId)?.get('filled')) { + return true; + } + return compartmentId && this.vectorSource.getFeatureById(compartmentId)?.get('filled'); }); } @@ -356,10 +364,8 @@ export default class Reaction { return undefined; } if (this.isAnyOfElementsHidden()) { - feature.set('hidden', true); return undefined; } - feature.set('hidden', false); const styles: Array<Style> = []; const scale = this.minResolution / resolution; @@ -392,10 +398,8 @@ export default class Reaction { return undefined; } if (this.isAnyOfElementsHidden()) { - feature.set('hidden', true); return undefined; } - feature.set('hidden', false); const styles: Array<Style> = []; const style = feature.get('style'); -- GitLab From b6e5677203a5cf5503f21c8b5b2ebaa41fa852db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl> Date: Thu, 12 Dec 2024 13:34:43 +0100 Subject: [PATCH 6/9] fix(semantic-view): add a function that checks whether a feature in a compartment --- .../shapes/elements/handleSemanticView.ts | 7 +- .../elements/isFeatureInCompartment.test.ts | 80 +++++++++++++++++++ .../shapes/elements/isFeatureInCompartment.ts | 21 +++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/isFeatureInCompartment.test.ts create mode 100644 src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/isFeatureInCompartment.ts diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts index d1007e7b..22492167 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts @@ -5,6 +5,7 @@ import Feature from 'ol/Feature'; import VectorSource from 'ol/source/Vector'; import { Extent } from 'ol/extent'; import { COMPLEX_SBO_TERMS } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; +import isFeatureInCompartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/isFeatureInCompartment'; export default function handleSemanticView({ vectorSource, @@ -21,6 +22,10 @@ export default function handleSemanticView({ compartmentId: number | null; complexId?: number | null; }): { cover: boolean; hide: boolean; largestExtent: Extent | null } { + const featureId = feature.getId(); + if (!featureId) { + return { cover: false, hide: false, largestExtent: null }; + } const type = feature.get('type'); const getMapExtent = feature.get('getMapExtent'); let coverRatio = 1; @@ -47,7 +52,7 @@ export default function handleSemanticView({ intersectingFeature.get('type') === 'COMPARTMENT' && intersectingFeature.get('zIndex') > feature.get('zIndex') && intersectingFeature.get('filled') && - intersectingFeature.get('compartmentId') !== feature.getId() + !isFeatureInCompartment(+featureId, vectorSource, intersectingFeature) ) { const intersectingFeatureExtent = intersectingFeature.getGeometry()?.getExtent(); if (intersectingFeatureExtent) { diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/isFeatureInCompartment.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/isFeatureInCompartment.test.ts new file mode 100644 index 00000000..58c9523c --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/isFeatureInCompartment.test.ts @@ -0,0 +1,80 @@ +/* eslint-disable no-magic-numbers */ +import VectorSource from 'ol/source/Vector'; +import Feature from 'ol/Feature'; +import isFeatureInCompartment from './isFeatureInCompartment'; + +describe('isFeatureInCompartment', () => { + let mockVectorSource: jest.Mocked<VectorSource>; + + beforeEach(() => { + mockVectorSource = { + getFeatureById: jest.fn(), + } as unknown as jest.Mocked<VectorSource>; + }); + + it('should return false if the feature has no compartmentId', () => { + const feature = new Feature(); + feature.set('compartmentId', null); + + const result = isFeatureInCompartment(1, mockVectorSource, feature); + + expect(result).toBe(false); + }); + + it('should return true if the feature compartmentId matches parentCompartmentId', () => { + const feature = new Feature(); + feature.set('compartmentId', 1); + + const result = isFeatureInCompartment(1, mockVectorSource, feature); + + expect(result).toBe(true); + }); + + it('should return false if the parent feature does not exist', () => { + const feature = new Feature(); + feature.set('compartmentId', 2); + + mockVectorSource.getFeatureById.mockReturnValueOnce(null); + + const result = isFeatureInCompartment(1, mockVectorSource, feature); + + expect(result).toBe(false); + expect(mockVectorSource.getFeatureById).toHaveBeenCalledWith(2); + }); + + it('should return true if parent feature matches parentCompartmentId in recursive call', () => { + const parentFeature = new Feature(); + parentFeature.set('compartmentId', 1); + + const childFeature = new Feature(); + childFeature.set('compartmentId', 2); + + mockVectorSource.getFeatureById.mockReturnValueOnce(parentFeature); + + const result = isFeatureInCompartment(1, mockVectorSource, childFeature); + + expect(result).toBe(true); + expect(mockVectorSource.getFeatureById).toHaveBeenCalledWith(2); + }); + + it('should return false if recursive call finds no matching parent', () => { + const grandParentFeature = new Feature(); + grandParentFeature.set('compartmentId', 3); + + const parentFeature = new Feature(); + parentFeature.set('compartmentId', 2); + + const childFeature = new Feature(); + childFeature.set('compartmentId', 4); + + mockVectorSource.getFeatureById + .mockReturnValueOnce(parentFeature) + .mockReturnValueOnce(grandParentFeature); + + const result = isFeatureInCompartment(1, mockVectorSource, childFeature); + + expect(result).toBe(false); + expect(mockVectorSource.getFeatureById).toHaveBeenCalledWith(4); + expect(mockVectorSource.getFeatureById).toHaveBeenCalledWith(2); + }); +}); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/isFeatureInCompartment.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/isFeatureInCompartment.ts new file mode 100644 index 00000000..15f6c6ad --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/isFeatureInCompartment.ts @@ -0,0 +1,21 @@ +import VectorSource from 'ol/source/Vector'; +import Feature from 'ol/Feature'; + +export default function isFeatureInCompartment( + parentCompartmentId: number, + vectorSource: VectorSource, + feature: Feature, +): boolean { + const compartmentId: undefined | null | number = feature.get('compartmentId'); + if (!compartmentId) { + return false; + } + if (compartmentId === parentCompartmentId) { + return true; + } + const compartmentFeature = vectorSource.getFeatureById(compartmentId); + if (!compartmentFeature) { + return false; + } + return isFeatureInCompartment(parentCompartmentId, vectorSource, compartmentFeature); +} -- GitLab From 8de23a71194a99132129f125025e9c8f9a06c26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl> Date: Fri, 13 Dec 2024 11:08:40 +0100 Subject: [PATCH 7/9] fix(vector-map): use constants istead of hardcoded values --- .../MapViewerVector.constants.ts | 35 +++++++++++-------- .../utils/shapes/elements/BaseMultiPolygon.ts | 20 ++++++++--- .../utils/shapes/elements/Compartment.ts | 2 +- .../shapes/elements/CompartmentPathway.ts | 2 +- .../shapes/elements/handleSemanticView.ts | 5 +-- .../utils/shapes/layer/Layer.ts | 3 +- .../utils/shapes/reaction/Reaction.ts | 9 +++-- .../MapViewerVector/utils/shapes/text/Text.ts | 12 +++---- 8 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts index 5bc52a7f..614d3dcf 100644 --- a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts +++ b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts @@ -4,6 +4,11 @@ export const VECTOR_MAP_LAYER_TYPE = 'vectorMapLayer'; export const COMPLEX_SBO_TERMS = ['SBO:0000253', 'SBO:0000297', 'SBO:0000289']; +export const TEXT_CUTOFF_SCALE = 0.34; +export const OUTLINE_CUTOFF_SCALE = 0.18; +export const COMPLEX_CONTENTS_CUTOFF_SCALE = 0.215; +export const REACTION_ELEMENT_CUTOFF_SCALE = 0.125; + export const WHITE_COLOR: Color = { alpha: 255, rgb: 16777215, @@ -20,27 +25,27 @@ export const TRANSPARENT_COLOR: Color = { }; export const REACTION_ELEMENT_TYPES = { - OPERATOR: 'operator', - SQUARE: 'square', - LINE: 'line', - ARROW: 'arrow', + OPERATOR: 'OPERATOR', + SQUARE: 'SQUARE', + LINE: 'LINE', + ARROW: 'ARROW', }; export const MAP_ELEMENT_TYPES = { - TEXT: 'text', - MODIFICATION: 'modification', - ACTIVITY_BORDER: 'activityBorder', - ENTITY: 'entity', - OVERLAY: 'overlay', - COMPARTMENT: 'compartment', + TEXT: 'TEXT', + MODIFICATION: 'MODIFICATION', + ACTIVITY_BORDER: 'ACTIVITY_BORDER', + ENTITY: 'ENTITY', + OVERLAY: 'OVERLAY', + COMPARTMENT: 'COMPARTMENT', }; export const LAYER_ELEMENT_TYPES = { - TEXT: 'text', - OVAL: 'oval', - RECT: 'rect', - LINE: 'line', - ARROW: 'arrow', + TEXT: 'TEXT', + OVAL: 'OVAL', + RECT: 'RECT', + LINE: 'LINE', + ARROW: 'ARROW', }; export const COMPARTMENT_SQUARE_POINTS: Array<ShapeRelAbs | ShapeRelAbsBezierPoint> = [ 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 fd2f74fd..ed6fe198 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts @@ -14,8 +14,11 @@ import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shape import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords'; import { Color } from '@/types/models'; import { + COMPLEX_CONTENTS_CUTOFF_SCALE, COMPLEX_SBO_TERMS, MAP_ELEMENT_TYPES, + OUTLINE_CUTOFF_SCALE, + TEXT_CUTOFF_SCALE, } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; import VectorSource from 'ol/source/Vector'; import MapBackgroundsEnum from '@/redux/map/map.enums'; @@ -258,14 +261,18 @@ export default abstract class BaseMultiPolygon { let cover = false; let largestExtent: Extent | null; - if (this.complexId && !COMPLEX_SBO_TERMS.includes(this.sboTerm) && scale < 0.215) { + if ( + this.complexId && + !COMPLEX_SBO_TERMS.includes(this.sboTerm) && + scale < COMPLEX_CONTENTS_CUTOFF_SCALE + ) { feature.set('hidden', true); return []; } feature.set('hidden', false); let hide = false; - if (this.mapBackgroundType === MapBackgroundsEnum.SEMANTIC && scale < 0.34) { + if (this.mapBackgroundType === MapBackgroundsEnum.SEMANTIC && scale < TEXT_CUTOFF_SCALE) { const semanticViewData = handleSemanticView( this.vectorSource, feature, @@ -310,19 +317,22 @@ export default abstract class BaseMultiPolygon { return; } - if ([MAP_ELEMENT_TYPES.MODIFICATION, MAP_ELEMENT_TYPES.TEXT].includes(type) && scale < 0.34) { + if ( + [MAP_ELEMENT_TYPES.MODIFICATION, MAP_ELEMENT_TYPES.TEXT].includes(type) && + scale < TEXT_CUTOFF_SCALE + ) { return; } const textStyle = style.getText(); - if (type === 'text' && textStyle) { + if (type === MAP_ELEMENT_TYPES.TEXT && textStyle) { textStyle.setScale(scale); } if (strokeStyle) { const lineWidth = strokeStyle.getWidth() || 1; if ( !this.overlaysVisible && - scale < 0.18 && + scale < OUTLINE_CUTOFF_SCALE && !COMPLEX_SBO_TERMS.includes(this.sboTerm) && this.type !== MAP_ELEMENT_TYPES.COMPARTMENT ) { 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 8906e75d..987fd2ce 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts @@ -97,7 +97,7 @@ export default abstract class Compartment extends BaseMultiPolygon { mapSize, }: CompartmentProps) { super({ - type: 'COMPARTMENT', + type: MAP_ELEMENT_TYPES.COMPARTMENT, id, complexId, compartmentId, 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 e758a681..2850176f 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts @@ -87,7 +87,7 @@ export default class CompartmentPathway extends BaseMultiPolygon { mapSize, }: CompartmentPathwayProps) { super({ - type: 'COMPARTMENT', + type: MAP_ELEMENT_TYPES.COMPARTMENT, id, complexId, compartmentId, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts index 19619484..b329d609 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts @@ -4,6 +4,7 @@ import findLargestExtent from '@/components/Map/MapViewer/MapViewerVector/utils/ import Feature from 'ol/Feature'; import VectorSource from 'ol/source/Vector'; import { Extent } from 'ol/extent'; +import { MAP_ELEMENT_TYPES } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; export default function handleSemanticView( vectorSource: VectorSource, @@ -18,7 +19,7 @@ export default function handleSemanticView( let cover = false; let hide = false; let largestExtent: Extent | null = null; - if (getMapExtent instanceof Function && type === 'COMPARTMENT') { + if (getMapExtent instanceof Function && type === MAP_ELEMENT_TYPES.COMPARTMENT) { const mapExtent = getMapExtent(resolution); const featureExtent = feature.getGeometry()?.getExtent(); if (featureExtent && mapExtent) { @@ -33,7 +34,7 @@ export default function handleSemanticView( vectorSource.forEachFeatureIntersectingExtent(featureExtent, intersectingFeature => { if ( !intersectingFeature.get('hidden') && - intersectingFeature.get('type') === 'COMPARTMENT' && + intersectingFeature.get('type') === MAP_ELEMENT_TYPES.COMPARTMENT && intersectingFeature.get('zIndex') > feature.get('zIndex') && intersectingFeature.get('filled') ) { 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 29f17e00..0b8d6403 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts @@ -21,6 +21,7 @@ import Style from 'ol/style/Style'; import { ArrowTypeDict, LineTypeDict } from '@/redux/shapes/shapes.types'; import { LAYER_ELEMENT_TYPES, + REACTION_ELEMENT_CUTOFF_SCALE, TRANSPARENT_COLOR, } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; import getScaledElementStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle'; @@ -305,7 +306,7 @@ export default class Layer { let strokeStyle: Stroke | undefined; const type = feature.get('elementType'); - if (type === LAYER_ELEMENT_TYPES.ARROW && scale <= 0.08) { + if (type === LAYER_ELEMENT_TYPES.ARROW && scale <= REACTION_ELEMENT_CUTOFF_SCALE) { return []; } 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 0a611adc..a2a4a38a 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts @@ -7,7 +7,9 @@ import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/st import Polygon from 'ol/geom/Polygon'; import Style from 'ol/style/Style'; import { + REACTION_ELEMENT_CUTOFF_SCALE, REACTION_ELEMENT_TYPES, + TEXT_CUTOFF_SCALE, WHITE_COLOR, } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; import { FeatureLike } from 'ol/Feature'; @@ -366,10 +368,7 @@ export default class Reaction { const type = feature.get('elementType'); let strokeStyle: Stroke | undefined; - if (type === REACTION_ELEMENT_TYPES.OPERATOR && scale < 0.34) { - return []; - } - if (type === REACTION_ELEMENT_TYPES.ARROW && scale <= 0.125) { + if (type === REACTION_ELEMENT_TYPES.OPERATOR && scale < TEXT_CUTOFF_SCALE) { return []; } @@ -402,7 +401,7 @@ export default class Reaction { const scale = this.minResolution / resolution; let strokeStyle: Stroke | undefined; - if (scale <= 0.125) { + if (scale <= REACTION_ELEMENT_CUTOFF_SCALE) { return []; } diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.ts index 6d84b083..27b8ec6f 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/text/Text.ts @@ -13,6 +13,7 @@ import { MapInstance } from '@/types/map'; import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords'; import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle'; import { Color } from '@/types/models'; +import { TEXT_CUTOFF_SCALE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; export interface TextProps { x: number; @@ -101,15 +102,10 @@ export default class Text { if (getTextScale instanceof Function) { textScale = getTextScale(resolution); } - - if (this.style.getText()) { - if (this.fontSize * textScale > 4) { - this.style.getText()?.setScale(textScale); - this.style.getText()?.setText(this.text); - } else { - this.style.getText()?.setText(undefined); - } + if (textScale < TEXT_CUTOFF_SCALE) { + return undefined; } + this.style.getText()?.setScale(textScale); return this.style; } } -- GitLab From 2f8b7c22689aec82735fccecf94042f146f009c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl> Date: Mon, 16 Dec 2024 08:26:51 +0100 Subject: [PATCH 8/9] feat(vector-map): restore old map extent --- .../MapViewer/utils/config/useOlMapView.ts | 29 +++++++------------ src/constants/map.ts | 1 - 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.ts index d76947fd..a639ebd1 100644 --- a/src/components/Map/MapViewer/utils/config/useOlMapView.ts +++ b/src/components/Map/MapViewer/utils/config/useOlMapView.ts @@ -1,5 +1,5 @@ /* eslint-disable no-magic-numbers */ -import { DEFAULT_EXTENT_PADDING, OPTIONS, ZOOM_RESCALING_FACTOR } from '@/constants/map'; +import { EXTENT_PADDING_MULTIPLICATOR, OPTIONS, ZOOM_RESCALING_FACTOR } from '@/constants/map'; import { mapDataInitialPositionSelector, mapDataSizeSelector } from '@/redux/map/map.selectors'; import { MapInstance, Point } from '@/types/map'; import { usePointToProjection } from '@/utils/map/usePointToProjection'; @@ -19,32 +19,23 @@ export const useOlMapView = ({ mapInstance }: UseOlMapViewInput): MapConfig['vie const pointToProjection = usePointToProjection(); const extent = useMemo((): Extent => { - let widthPadding = 0; - let heightPadding = 0; - let mapInstanceWidthToHeightRatio = 2.15; - const mapInstanceSize = mapInstance?.getSize(); - if (mapInstanceSize) { - mapInstanceWidthToHeightRatio = mapInstanceSize[0] / mapInstanceSize[1]; - } - const mapWidthToHeightRatio = mapSize.width / mapSize.height; + const extentPadding = { + horizontal: mapSize.width * EXTENT_PADDING_MULTIPLICATOR, + vertical: mapSize.height * EXTENT_PADDING_MULTIPLICATOR, + }; - if (mapWidthToHeightRatio < mapInstanceWidthToHeightRatio) { - widthPadding = mapSize.height * mapInstanceWidthToHeightRatio - mapSize.width; - } else { - heightPadding = mapSize.width / mapInstanceWidthToHeightRatio - mapSize.height; - } const topLeftPoint: Point = { - x: mapSize.width + widthPadding / 2 + DEFAULT_EXTENT_PADDING, - y: mapSize.height + heightPadding / 2 + DEFAULT_EXTENT_PADDING, + x: mapSize.width + extentPadding.horizontal, + y: mapSize.height + extentPadding.vertical, }; const bottomRightPoint: Point = { - x: -widthPadding / 2 - DEFAULT_EXTENT_PADDING, - y: -heightPadding / 2 - DEFAULT_EXTENT_PADDING, + x: -extentPadding.horizontal, + y: -extentPadding.vertical, }; return boundingExtent([topLeftPoint, bottomRightPoint].map(pointToProjection)); - }, [mapSize.width, mapSize.height, mapInstance, pointToProjection]); + }, [pointToProjection, mapSize]); const center = useMemo((): Point => { const centerPoint: Point = { diff --git a/src/constants/map.ts b/src/constants/map.ts index c995fdad..a77a62c8 100644 --- a/src/constants/map.ts +++ b/src/constants/map.ts @@ -11,7 +11,6 @@ export const DEFAULT_CENTER_Y = 0; // eslint-disable-next-line no-magic-numbers export const LATLNG_FALLBACK: LatLng = [0, 0]; export const EXTENT_PADDING_MULTIPLICATOR = 1; -export const DEFAULT_EXTENT_PADDING = 20; export const ZOOM_RESCALING_FACTOR = 1; export const DEFAULT_CENTER_POINT: Point = { -- GitLab From 915efda8ea856dda8653577de6820725402cf9a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Grocholewski?= <m.grocholewski@atcomp.pl> Date: Mon, 16 Dec 2024 09:53:08 +0100 Subject: [PATCH 9/9] feat(vector-map): split getStyle function into separate smaller functions --- .../utils/shapes/elements/BaseMultiPolygon.ts | 157 +++++++++++------- .../utils/shapes/elements/Compartment.ts | 8 +- .../shapes/elements/CompartmentPathway.ts | 4 +- .../utils/shapes/elements/MapElement.ts | 8 +- 4 files changed, 107 insertions(+), 70 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 553893c4..dd541d11 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts @@ -22,7 +22,6 @@ 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'; @@ -105,6 +104,14 @@ export default abstract class BaseMultiPolygon { styles: Array<Style> = []; + overlaysPolygons: Array<Polygon> = []; + + overlaysStyles: Array<Style> = []; + + coverStyle: Style | undefined; + + coverStrokeStyle: Stroke | undefined; + feature: Feature = new Feature(); pointToProjection: UsePointToProjectionResult; @@ -216,7 +223,7 @@ export default abstract class BaseMultiPolygon { protected drawMultiPolygonFeature(mapInstance: MapInstance): void { this.feature = new Feature({ - geometry: new MultiPolygon(this.polygons), + geometry: new MultiPolygon([...this.polygons, ...this.overlaysPolygons]), zIndex: this.zIndex, getMapExtent: (resolution: number): [number, number, number, number] | undefined => { if (this.mapExtentCache.has(resolution)) { @@ -253,15 +260,38 @@ export default abstract class BaseMultiPolygon { this.feature.setStyle(this.getStyle.bind(this)); } - protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void { - if (!(feature instanceof Feature)) { - return undefined; + protected setStrokeStyle(scale: number, style: Style, strokeStyle: Stroke): void { + if ( + !this.overlaysVisible && + scale < OUTLINE_CUTOFF_SCALE && + !COMPLEX_SBO_TERMS.includes(this.sboTerm) && + this.type !== MAP_ELEMENT_TYPES.COMPARTMENT + ) { + style.setStroke(null); + } else { + style.setStroke(getScaledStrokeStyle(strokeStyle, scale)); } + } + + protected processOverlayStyle(scale: number): Array<Style> { + let strokeStyle: Stroke | undefined; const styles: Array<Style> = []; - const scale = this.minResolution / resolution; - let cover = false; - let largestExtent: Extent | null; + this.overlaysStyles.forEach(style => { + const styleGeometry = style.getGeometry(); + if (styleGeometry instanceof Polygon) { + strokeStyle = styleGeometry.get('strokeStyle'); + } + + if (strokeStyle) { + this.setStrokeStyle(scale, style, strokeStyle); + } + styles.push(style); + }); + return styles; + } + + protected processElementStyles(scale: number): Array<Style> { if ( this.complexId && !COMPLEX_SBO_TERMS.includes(this.sboTerm) && @@ -270,54 +300,17 @@ export default abstract class BaseMultiPolygon { return []; } - let hide = false; - if (this.mapBackgroundType === MapBackgroundsEnum.SEMANTIC && scale < TEXT_CUTOFF_SCALE) { - const semanticViewData = handleSemanticView({ - vectorSource: this.vectorSource, - feature, - resolution, - sboTerm: this.sboTerm, - compartmentId: this.compartmentId, - complexId: this.complexId, - }); - cover = semanticViewData.cover; - hide = semanticViewData.hide; - largestExtent = semanticViewData.largestExtent; - - if (hide) { - return undefined; - } - } - - let type: string; - let coverStyle: Style | undefined; let strokeStyle: Stroke | undefined; + let type: string; + const styles: Array<Style> = []; this.styles.forEach(style => { const styleGeometry = style.getGeometry(); if (styleGeometry instanceof Polygon) { type = styleGeometry.get('type'); - coverStyle = styleGeometry.get('coverStyle'); strokeStyle = styleGeometry.get('strokeStyle'); } - if (cover) { - if (coverStyle && largestExtent) { - styles.push( - ...getCoverStyles({ - coverStyle, - largestExtent, - text: this.text, - scale, - zIndex: this.zIndex + 100000, - mapSize: this.mapSize, - strokeStyle, - }), - ); - } - return; - } - if ( [MAP_ELEMENT_TYPES.MODIFICATION, MAP_ELEMENT_TYPES.TEXT].includes(type) && scale < TEXT_CUTOFF_SCALE @@ -330,19 +323,69 @@ export default abstract class BaseMultiPolygon { textStyle.setScale(scale); } if (strokeStyle) { - if ( - !this.overlaysVisible && - scale < OUTLINE_CUTOFF_SCALE && - !COMPLEX_SBO_TERMS.includes(this.sboTerm) && - this.type !== MAP_ELEMENT_TYPES.COMPARTMENT - ) { - style.setStroke(null); - } else { - style.setStroke(getScaledStrokeStyle(strokeStyle, scale)); - } + this.setStrokeStyle(scale, style, strokeStyle); } styles.push(style); }); + return styles; + } + + protected processSemanticView( + feature: Feature, + resolution: number, + scale: number, + ): { hide: boolean; coverStyle: Array<Style> | null } { + let coverStyle = null; + const semanticViewData = handleSemanticView({ + vectorSource: this.vectorSource, + feature, + resolution, + sboTerm: this.sboTerm, + compartmentId: this.compartmentId, + complexId: this.complexId, + }); + const { cover } = semanticViewData; + const { hide } = semanticViewData; + const { largestExtent } = semanticViewData; + + if (hide) { + return { hide, coverStyle }; + } + + if (cover && largestExtent && this.coverStyle) { + coverStyle = getCoverStyles({ + coverStyle: this.coverStyle, + largestExtent, + text: this.text, + scale, + zIndex: this.zIndex + 100000, + mapSize: this.mapSize, + strokeStyle: this.coverStrokeStyle, + }); + } + + return { hide, coverStyle }; + } + + protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void { + if (!(feature instanceof Feature)) { + return undefined; + } + const styles: Array<Style> = []; + const scale = this.minResolution / resolution; + + if (this.mapBackgroundType === MapBackgroundsEnum.SEMANTIC && scale < TEXT_CUTOFF_SCALE) { + const { hide, coverStyle } = this.processSemanticView(feature, resolution, scale); + if (hide) { + return undefined; + } + if (coverStyle) { + return coverStyle; + } + } + + styles.push(...this.processOverlayStyle(scale)); + styles.push(...this.processElementStyles(scale)); 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 987fd2ce..75c519ef 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts @@ -139,16 +139,10 @@ export default abstract class Compartment extends BaseMultiPolygon { protected createPolygons(): void { const coverPolygon = new Polygon([this.outerCoords]); - const coverStyle = new Style({ + this.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); 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 2850176f..eef5c0f7 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts @@ -133,11 +133,11 @@ export default class CompartmentPathway extends BaseMultiPolygon { ], ]); compartmentPolygon.set('type', MAP_ELEMENT_TYPES.COMPARTMENT); - const coverStyle = new Style({ + this.coverStyle = new Style({ geometry: compartmentPolygon, fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 255 }) }), }); - compartmentPolygon.set('coverStyle', coverStyle); + compartmentPolygon.set( 'strokeStyle', getStroke({ 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 a8ee5425..38b6b16d 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts @@ -319,12 +319,12 @@ export default class MapElement extends BaseMultiPolygon { }); elementPolygon.set('strokeStyle', strokeStyle); if (COMPLEX_SBO_TERMS.includes(this.sboTerm)) { - const coverStyle = new Style({ + this.coverStyle = new Style({ geometry: elementPolygon, fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 255 }) }), stroke: strokeStyle, }); - elementPolygon.set('coverStyle', coverStyle); + this.coverStrokeStyle = strokeStyle; } this.polygons.push(elementPolygon); this.styles.push(elementStyle); @@ -365,8 +365,8 @@ export default class MapElement extends BaseMultiPolygon { color, }), ); - this.polygons.push(polygon); - this.styles.push(style); + this.overlaysPolygons.push(polygon); + this.overlaysStyles.push(style); }); } } -- GitLab