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 62ad872d82eb2c552ff24d895e3a010f33049a8d..3a33837b1a82b2f8999bc503374096f15babaf38 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 2822685ff613e8d5221f8ce3bd83003734a83046..74e10fc213a8365f5da3c26be566d0e5d405bc5e 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 9fb7e48d0c510eaa35e97a95e7e1f33e91ac25db..bcf5cac42c328c5f93f416344e39817c802a7588 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 ed6fe198504b2f721fa0c382302c58e46fc35eac..553893c422c52f5565777cfc798ec7b4d5d8c0cf 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts @@ -26,6 +26,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,20 +267,19 @@ export default abstract class BaseMultiPolygon { !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 < TEXT_CUTOFF_SCALE) { - 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; @@ -304,14 +304,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; @@ -329,7 +330,6 @@ export default abstract class BaseMultiPolygon { textStyle.setScale(scale); } if (strokeStyle) { - const lineWidth = strokeStyle.getWidth() || 1; if ( !this.overlaysVisible && scale < OUTLINE_CUTOFF_SCALE && @@ -338,18 +338,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 15a15c3e1d83125baf7fc92589cf8ad2c76e3d19..a8ee5425b17bb4ceff214cccd31dcfbca218c891 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 3c4ae01b5b599c028b8163c4cbcee3129f607bfb..f6360b51903d9bd27d0e6e959271b59562328421 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', @@ -50,7 +52,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: 2, + }); expect(result).toEqual({ cover: true, @@ -63,21 +71,26 @@ 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)); - 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, 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', () => { @@ -86,14 +99,18 @@ 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, 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 b329d6095233df50f1621f858dacdec526ea6bdf..71c32e6fa89bb7ceb88958845feb10183ddef4fd 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/handleSemanticView.ts @@ -4,22 +4,41 @@ 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'; +import { + COMPLEX_SBO_TERMS, + MAP_ELEMENT_TYPES, +} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; +import isFeatureInCompartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/isFeatureInCompartment'; -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 featureId = feature.getId(); + if (!featureId) { + return { cover: false, hide: true, largestExtent: 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 === MAP_ELEMENT_TYPES.COMPARTMENT) { + if ( + getMapExtent instanceof Function && + (type === MAP_ELEMENT_TYPES.COMPARTMENT || COMPLEX_SBO_TERMS.includes(sboTerm)) + ) { const mapExtent = getMapExtent(resolution); const featureExtent = feature.getGeometry()?.getExtent(); if (featureExtent && mapExtent) { @@ -33,10 +52,10 @@ export default function handleSemanticView( let remainingExtents = [featureExtent]; vectorSource.forEachFeatureIntersectingExtent(featureExtent, intersectingFeature => { if ( - !intersectingFeature.get('hidden') && intersectingFeature.get('type') === MAP_ELEMENT_TYPES.COMPARTMENT && intersectingFeature.get('zIndex') > feature.get('zIndex') && - intersectingFeature.get('filled') + intersectingFeature.get('filled') && + !isFeatureInCompartment(+featureId, vectorSource, intersectingFeature) ) { const intersectingFeatureExtent = intersectingFeature.getGeometry()?.getExtent(); if (intersectingFeatureExtent) { @@ -52,7 +71,7 @@ export default function handleSemanticView( if (complexId) { const complex = vectorSource.getFeatureById(complexId); - if (complex && complex.get('hidden')) { + if (complex && complex.get('filled')) { hide = true; } } @@ -62,7 +81,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/elements/isFeatureInCompartment.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/isFeatureInCompartment.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..58c9523c0e5e8188f16c3c567f1ed192671cf7b9 --- /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 0000000000000000000000000000000000000000..15f6c6add2f1eaa414b21e7b843e6982306a51e8 --- /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); +} 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 a2a4a38ab8c1d98abedbf7b4071a1c860204aca1..ccefcd64da957e5a1ffef93fcb9398108eb54017 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts @@ -349,7 +349,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'); }); } @@ -358,10 +366,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; @@ -391,10 +397,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'); 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 0e22293c5bdb85bd53191ed7d3a784a48310f5e0..8178ab4a339145fa56fe6965dc97ea3f673f5941 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 ce072c86cc472abb701f351e0d09a4c7dc2e1e3b..78f2ef6b8d27d1283faf6ad89c00bd46636cfcc6 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 0000000000000000000000000000000000000000..5c22d99435b0030dee701f1c765177e0108d10e3 --- /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 27c0c5157ed279f2649869eb27a016fd6628856a..a91c03094074ba2531f6af8fbb0c59e185fd28b6 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 0000000000000000000000000000000000000000..759b88e537da449b6fabb0e86221e33f2f6ddb75 --- /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 0000000000000000000000000000000000000000..111adc06c70a9131092a7d516ed16ecf7d94b7d9 --- /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, + }); +}